Como fazer o processo filho morrer após a saída dos pais?


209

Suponha que eu tenha um processo que gera exatamente um processo filho. Agora, quando o processo pai termina por qualquer motivo (normalmente ou de forma anormal, por morte, ^ C, afirmar falha ou qualquer outra coisa), quero que o processo filho morra. Como fazer isso corretamente?


Alguma pergunta semelhante sobre o stackoverflow:


Alguma pergunta semelhante sobre o stackoverflow para Windows :

Respostas:


189

A criança pode pedir ao kernel para entregar SIGHUP(ou outro sinal) quando o pai morre, especificando a opção PR_SET_PDEATHSIGno prctl()syscall da seguinte maneira:

prctl(PR_SET_PDEATHSIG, SIGHUP);

Veja man 2 prctlpara detalhes.

Edit: Este é apenas para Linux


5
Esta é uma solução ruim porque os pais já devem ter morrido. Condição de corrida. Solução correta: stackoverflow.com/a/17589555/412080
Maxim Egorushkin

16
Dar uma resposta ruim não é muito bom - mesmo que não atenda às condições da corrida. Veja minha resposta sobre como usar de prctl()maneira livre de condições de corrida. Aliás, a resposta vinculada por Maxim está incorreta.
maxschlepzig

4
Este é apenas um anser errado. Ele enviará o sinal para o processo filho no momento em que o encadeamento chamado garfo morre, e não quando o processo pai morre.
Lothar

2
@ Lothar Seria bom ver algum tipo de prova. man prctldiz: Defina o sinal de morte do processo pai do processo de chamada como arg2 (um valor de sinal no intervalo 1..maxsig ou 0 para limpar). Este é o sinal que o processo de chamada receberá quando seu pai morrer. Este valor é limpo para o filho de um fork (2) e (desde o Linux 2.4.36 / 2.6.23) ao executar um binário set-user-ID ou set-group-ID.
qrdl

1
@maxschlepzig Obrigado pelo novo link. Parece que o link anterior é inválido. A propósito, depois de anos, ainda não há API para definir opções no lado dos pais. Que pena.
quer

68

Estou tentando resolver o mesmo problema e, como meu programa deve ser executado no OS X, a solução somente para Linux não funcionou para mim.

Cheguei à mesma conclusão que as outras pessoas nesta página - não há uma maneira compatível com POSIX de notificar uma criança quando um pai morre. Por isso, julguei a melhor coisa - fazer a pesquisa infantil.

Quando um processo pai morre (por qualquer motivo), o processo pai da criança se torna o processo 1. Se a criança simplesmente pesquisar periodicamente, poderá verificar se seu pai é 1. Se for, o filho deve sair.

Isso não é ótimo, mas funciona e é mais fácil do que as soluções de pesquisa de soquete / arquivo de bloqueio TCP sugeridas em outras partes desta página.


6
Excelente solução. Invocando continuamente getppid () até retornar 1 e depois sair. Isso é bom e agora eu também o uso. Uma solução não-pollig seria legal. Obrigado Schof.
neoneye

10
Apenas para obter informações, no Solaris, se você estiver em uma zona, gettpid()ele não se torna 1, mas obtém o pidagendador da zona (processo zsched).
Patrick Schlüter

4
Se alguém está se perguntando, nos sistemas Android, o pid parece ser 0 (processar System pid) em vez de 1, quando o pai morre.
Rui Marques

2
Para ter uma maneira mais robusta e independente de plataforma, antes de fork (), simplesmente getpid () e se getppid () de child for diferente, saia.
Sebastien

2
Isso não funciona se você não controlar o processo filho. Por exemplo, estou trabalhando em um comando que agrupa find (1) e quero garantir que a localização seja morta se o wrapper morrer por algum motivo.
Lucretiel 19/06/19

34

Eu consegui isso no passado executando o código "original" no "filho" e o código "gerado" no "pai" (ou seja: você inverte o sentido usual do teste depois fork()). Em seguida, prenda o SIGCHLD no código "gerado" ...

Pode não ser possível no seu caso, mas bonito quando funciona.


Solução muito boa, obrigado! O atualmente aceito é mais genérico, mas o seu é mais portátil.
Paweł Hajdan 12/11/08

1
O grande problema em fazer o trabalho no pai é que você está alterando o processo pai. No caso de um servidor que precise executar "para sempre", isso não é uma opção.
Alexis Wilke

29

Se você não conseguir modificar o processo filho, tente algo como o seguinte:

int pipes[2];
pipe(pipes)
if (fork() == 0) {
    close(pipes[1]); /* Close the writer end in the child*/
    dup2(0, pipes[0]); /* Use reader end as stdin */
    exec("sh -c 'set -o monitor; child_process & read dummy; kill %1'")
}

close(pipes[0]); /* Close the reader end in the parent */

Isso executa o filho de dentro de um processo de shell com o controle de trabalho ativado. O processo filho é gerado em segundo plano. O shell aguarda uma nova linha (ou um EOF) e mata o filho.

Quando o pai morre - não importa qual seja o motivo -, ele fecha o final do tubo. O shell filho receberá um EOF da leitura e continuará a matar o processo filho em segundo plano.


2
Bom, mas cinco chamadas de sistema e um sh gerado em dez linhas de códigos me deixam um pouco cético em relação a esse desempenho de código.
Oleiade

+1. Você pode evitar dup2e assumir o comando stdin usando o read -usinalizador para ler um descritor de arquivo específico. Também adicionei um setpgid(0, 0)na criança para impedir que ela saia ao pressionar ^ C no terminal.
Greg Hewgill

A ordem dos argumentos da dup2()chamada está incorreta. Se você deseja usar pipes[0]como stdin, deve escrever em dup2(pipes[0], 0)vez de dup2(0, pipes[0]). É dup2(oldfd, newfd)onde a chamada fecha um newfd aberto anteriormente.
maxschlepzig

@Oleiade, eu concordo, especialmente desde que o sh gerou faz apenas mais um garfo para executar o processo de criança real ...
maxschlepzig

16

No Linux, você pode instalar um sinal de morte pai na criança, por exemplo:

#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#include <signal.h> // signals
#include <unistd.h> // fork()
#include <stdio.h>  // perror()

// ...

pid_t ppid_before_fork = getpid();
pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() != ppid_before_fork)
        exit(1);
    // continue child execution ...

Observe que armazenar o ID do processo pai antes da bifurcação e testá-lo no filho depois prctl()elimina uma condição de corrida entre prctl()e a saída do processo que chamou o filho.

Observe também que o sinal de morte dos pais da criança é apagado nos próprios filhos recém-criados. Não é afetado por um execve().

Esse teste pode ser simplificado se tivermos certeza de que o processo do sistema encarregado de adotar todos os órfãos tem PID 1:

pid_t pid = fork();
if (pid == -1) { perror(0); exit(1); }
if (pid) {
    ; // continue parent execution
} else {
    int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
    if (r == -1) { perror(0); exit(1); }
    // test in case the original parent exited just
    // before the prctl() call
    if (getppid() == 1)
        exit(1);
    // continue child execution ...

initPorém, confiar no processo do sistema e ter o PID 1 não é portátil. POSIX.1-2008 especifica :

O ID do processo pai de todos os processos filhos existentes e processos zumbis do processo de chamada deve ser definido como o ID do processo de um processo do sistema definido pela implementação. Ou seja, esses processos devem ser herdados por um processo especial do sistema.

Tradicionalmente, o processo do sistema que adota todos os órfãos é o PID 1, ou seja, init - que é o ancestral de todos os processos.

Em sistemas modernos como Linux ou FreeBSD, outro processo pode ter esse papel. Por exemplo, no Linux, um processo pode chamar prctl(PR_SET_CHILD_SUBREAPER, 1)para se estabelecer como um processo do sistema que herda todos os órfãos de qualquer um de seus descendentes (veja um exemplo no Fedora 25).


Não entendo "Esse teste pode ser simplificado se tivermos certeza de que o avô é sempre o processo de inicialização". Quando um processo pai morre, um processo se torna filho do processo init (pid 1), não filho do avô, certo? Portanto, o teste sempre parece estar correto.
Johannes Schaub - litb 26/11


interessante, obrigado. embora eu não consiga ver o que isso tem a ver com o avô.
Johannes Schaub - litb 28/11

1
@ JohannesSchaub-litb, você nem sempre pode assumir que o avô de um processo será um init(8)processo .... a única coisa que você pode assumir é que, quando um processo pai morre, é que seu ID pai será alterado. Na verdade, isso acontece uma vez na vida de um processo ... e é quando o pai do processo morre. Há apenas uma exceção principal para isso, e é para init(8)crianças, mas você está protegido contra isso, como init(8)não exit(2)(kernel panic nesse caso)
Luis Colorado

1
Infelizmente, se um filho bifurcar um thread e, em seguida, o thread sair, o processo filho obterá o SIGTERM.
rox

14

Por uma questão de integridade. No macOS, você pode usar o kqueue:

void noteProcDeath(
    CFFileDescriptorRef fdref, 
    CFOptionFlags callBackTypes, 
    void* info) 
{
    // LOG_DEBUG(@"noteProcDeath... ");

    struct kevent kev;
    int fd = CFFileDescriptorGetNativeDescriptor(fdref);
    kevent(fd, NULL, 0, &kev, 1, NULL);
    // take action on death of process here
    unsigned int dead_pid = (unsigned int)kev.ident;

    CFFileDescriptorInvalidate(fdref);
    CFRelease(fdref); // the CFFileDescriptorRef is no longer of any use in this example

    int our_pid = getpid();
    // when our parent dies we die as well.. 
    LOG_INFO(@"exit! parent process (pid %u) died. no need for us (pid %i) to stick around", dead_pid, our_pid);
    exit(EXIT_SUCCESS);
}


void suicide_if_we_become_a_zombie(int parent_pid) {
    // int parent_pid = getppid();
    // int our_pid = getpid();
    // LOG_ERROR(@"suicide_if_we_become_a_zombie(). parent process (pid %u) that we monitor. our pid %i", parent_pid, our_pid);

    int fd = kqueue();
    struct kevent kev;
    EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD|EV_ENABLE, NOTE_EXIT, 0, NULL);
    kevent(fd, &kev, 1, NULL, 0, NULL);
    CFFileDescriptorRef fdref = CFFileDescriptorCreate(kCFAllocatorDefault, fd, true, noteProcDeath, NULL);
    CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack);
    CFRunLoopSourceRef source = CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0);
    CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
}

Você pode fazer isso com uma API um pouco melhor, usando fontes de despacho com DISPATCH_SOURCE_PROC e PROC_EXIT.
russbishop 07/07

Por qualquer motivo, isso está causando pânico no meu Mac. A execução de um processo com esse código tem 50% de chance de congelar, fazendo com que os fãs girem a uma velocidade que eu nunca os ouvi antes (super rápido) e, em seguida, o mac simplesmente desliga. TENHA MUITO CUIDADO COM ESTE CÓDIGO .
Qix - MONICA FOI ERRADA EM

Parece que no meu macOS, o processo filho é encerrado automaticamente após a saída do pai. Não sei porque.
Yi Lin Liu

@YiLinLiu iirc eu usei NSTaskou posix spawn. Veja a startTaskfunção no meu código aqui: github.com/neoneye/newton-commander-browse/blob/master/Classes/…
neoneye

11

O processo filho possui um canal para / do processo pai? Nesse caso, você receberá um SIGPIPE se estiver escrevendo ou obterá um EOF ao ler - essas condições podem ser detectadas.


1
Achei isso não aconteceu de forma confiável, pelo menos no OS X.
Schof

Eu vim aqui para adicionar esta resposta. Essa é definitivamente a solução que eu usaria para este caso.
precisa saber é o seguinte

ponto de cautela: o systemd desabilita os SIGPIPE por padrão nos serviços que gerencia, mas você ainda pode verificar o fechamento do tubo. Veja freedesktop.org/software/systemd/man/systemd.exec.html em IgnoreSIGPIPE
jdizzle

11

Inspirado por outra resposta aqui, criei a seguinte solução all-POSIX. A idéia geral é criar um processo intermediário entre o pai e o filho, que tem um propósito: observe quando o pai morre e mate explicitamente o filho.

Esse tipo de solução é útil quando o código no filho não pode ser modificado.

int p[2];
pipe(p);
pid_t child = fork();
if (child == 0) {
    close(p[1]); // close write end of pipe
    setpgid(0, 0); // prevent ^C in parent from stopping this process
    child = fork();
    if (child == 0) {
        close(p[0]); // close read end of pipe (don't need it here)
        exec(...child process here...);
        exit(1);
    }
    read(p[0], 1); // returns when parent exits for any reason
    kill(child, 9);
    exit(1);
}

Existem duas pequenas advertências com este método:

  • Se você deliberadamente matar o processo intermediário, o filho não será morto quando o pai morrer.
  • Se o filho sair antes do pai, o processo intermediário tentará eliminar o pid filho original, que agora pode se referir a um processo diferente. (Isso pode ser corrigido com mais código no processo intermediário.)

Como um aparte, o código real que estou usando está em Python. Aqui está a integridade:

def run(*args):
    (r, w) = os.pipe()
    child = os.fork()
    if child == 0:
        os.close(w)
        os.setpgid(0, 0)
        child = os.fork()
        if child == 0:
            os.close(r)
            os.execl(args[0], *args)
            os._exit(1)
        os.read(r, 1)
        os.kill(child, 9)
        os._exit(1)
    os.close(r)

Observe que, um tempo atrás, no IRIX, eu usei um esquema pai / filho em que eu tinha um tubo entre os dois e a leitura do tubo gerou um SIGHUP se um deles morresse. Era assim que eu costumava matar meus filhos de garfo (), sem a necessidade de um processo intermediário.
Alexis Wilke

Eu acho que sua segunda ressalva está errada. O pid de um filho é um recurso pertencente ao pai e não pode ser liberado / reutilizado até que o pai (o processo intermediário) espere (ou termine e deixe o init aguardar).
R .. GitHub Pare de ajudar o gelo

7

Não acredito que seja possível garantir isso usando apenas chamadas POSIX padrão. Como na vida real, uma vez que uma criança é gerada, ela tem vida própria.

Ele é possível para o processo pai para pegar mais possíveis eventos de terminação, e tentar matar o processo filho nesse ponto, mas há sempre alguns que não pode ser capturado.

Por exemplo, nenhum processo pode pegar a SIGKILL. Quando o kernel lida com esse sinal, ele mata o processo especificado sem notificação para esse processo.

Para estender a analogia - a única outra maneira padrão de fazê-lo é cometer suicídio quando descobrir que não tem mais um pai ou mãe.

Existe uma maneira única de fazer isso com o Linux prctl(2)- veja outras respostas.


6

Como outras pessoas apontaram, contar com o pid dos pais para se tornar 1 quando os pais saem não é portátil. Em vez de esperar por um ID do processo pai específico, basta aguardar a alteração do ID:

pit_t pid = getpid();
switch (fork())
{
    case -1:
    {
        abort(); /* or whatever... */
    }
    default:
    {
        /* parent */
        exit(0);
    }
    case 0:
    {
        /* child */
        /* ... */
    }
}

/* Wait for parent to exit */
while (getppid() != pid)
    ;

Adicione um micro-sono conforme desejado, se você não quiser pesquisar a toda velocidade.

Essa opção me parece mais simples do que usar um tubo ou depender de sinais.


Infelizmente, essa solução não é robusta. E se o processo pai morrer antes que você obtenha o valor inicial? A criança nunca vai sair.
por dgatwood

@dgatwood, o que você quer dizer?!? O primeiro getpid()é feito no pai antes de chamar fork(). Se o pai morre antes que o filho não exista. O que pode acontecer é a criança viver fora dos pais por um tempo.
Alexis Wilke

Neste exemplo um tanto artificial, ele funciona, mas no código do mundo real, o fork é quase invariavelmente seguido pelo exec, e o novo processo deve começar novamente solicitando seu PPID. No período entre essas duas verificações, se o pai for embora, a criança não terá idéia. Além disso, é improvável que você tenha controle sobre o código pai e filho (ou então você pode simplesmente passar o PPID como argumento). Portanto, como solução geral, essa abordagem não funciona muito bem. E, realisticamente, se um sistema operacional UNIX parecesse sem o init ser 1, tantas coisas quebrariam que eu não consigo imaginar alguém fazendo isso de qualquer maneira.
por dgatwood

pass parent pid é um argumento de linha de comando ao executar exec for child.
Nish

2
A votação a toda velocidade é insana.
maxschlepzig

4

Instale um manipulador de armadilhas para capturar o SIGINT, que interrompe o processo filho se ele ainda estiver vivo, embora outros pôsteres estejam corretos e não capturem o SIGKILL.

Abra um arquivo .lock com acesso exclusivo e peça ao filho que ele tente abri-lo - se a abertura for bem-sucedida, o processo filho deve sair


Ou, a criança pode abrir o arquivo de bloqueio em um thread separado, no modo de bloqueio; nesse caso, essa pode ser uma solução bastante agradável e limpa. Provavelmente, existem algumas limitações de portabilidade.
5177 Jean

4

Esta solução funcionou para mim:

  • Passe o pipe stdin para o filho - você não precisa gravar nenhum dado no fluxo.
  • Criança lê indefinidamente de stdin até EOF. Um EOF sinaliza que o pai foi embora.
  • Esta é uma maneira infalível e portátil de detectar quando os pais se foram. Mesmo se o pai travar, o sistema operacional fechará o canal.

Isso era para um processo do tipo trabalhador cuja existência só fazia sentido quando o pai estava vivo.


@SebastianJylanki Não me lembro se tentei, mas provavelmente funciona porque as primitivas (fluxos POSIX) são bastante padrão nos sistemas operacionais.
Joonas.fi

3

Eu acho que uma maneira rápida e suja é criar um cano entre filho e pai. Quando os pais saem, os filhos recebem um SIGPIPE.


O SIGPIPE não é enviado no pipe perto, é enviado apenas quando a criança tenta gravar nele.
Alcaro

3

Alguns pôsteres já mencionaram cachimbos e kqueue. De fato, você também pode criar um par de soquetes de domínio Unix conectados pela socketpair()chamada. O tipo de soquete deve ser SOCK_STREAM.

Suponhamos que você tenha os dois descritores de arquivo de soquete fd1, fd2. Agora, fork()para criar o processo filho, que herdará o fds. No pai você fecha fd2 e no filho você fecha fd1. Agora, cada processo pode poll()abrir o fd restante por conta própria para o POLLINevento. Desde que cada lado não close()explique explicitamente o seu fd durante a vida normal, você pode ter certeza de que um POLLHUPsinalizador deve indicar o término do outro (não importa se está limpo ou não). Após a notificação deste evento, a criança pode decidir o que fazer (por exemplo, morrer).

#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <poll.h>
#include <stdio.h>

int main(int argc, char ** argv)
{
    int sv[2];        /* sv[0] for parent, sv[1] for child */
    socketpair(AF_UNIX, SOCK_STREAM, 0, sv);

    pid_t pid = fork();

    if ( pid > 0 ) {  /* parent */
        close(sv[1]);
        fprintf(stderr, "parent: pid = %d\n", getpid());
        sleep(100);
        exit(0);

    } else {          /* child */
        close(sv[0]);
        fprintf(stderr, "child: pid = %d\n", getpid());

        struct pollfd mon;
        mon.fd = sv[1];
        mon.events = POLLIN;

        poll(&mon, 1, -1);
        if ( mon.revents & POLLHUP )
            fprintf(stderr, "child: parent hung up\n");
        exit(0);
    }
}

Você pode tentar compilar o código de prova de conceito acima e executá-lo em um terminal como ./a.out &. Você tem aproximadamente 100 segundos para experimentar a eliminação do PID pai por vários sinais, ou ele simplesmente sai. Nos dois casos, você verá a mensagem "filho: pai desligou".

Comparado com o método usando SIGPIPEmanipulador, esse método não requer tentativa write().

Este método também é simétrico , ou seja, os processos podem usar o mesmo canal para monitorar a existência um do outro.

Esta solução chama apenas as funções POSIX. Eu tentei isso no Linux e no FreeBSD. Eu acho que deveria funcionar em outros Unixes, mas ainda não testei.

Veja também:

  • unix(7)de páginas de manual Linux, unix(4)FreeBSD, poll(2), socketpair(2), socket(7)no Linux.

Muito legal, eu estou realmente me perguntando se isso tem algum problema de confiabilidade. Você já testou isso em produção? Com aplicativos diferentes?
Aktau

@ Aktau, eu tenho usado o equivalente Python deste truque em um programa Linux. Eu precisava disso porque a lógica de trabalho da criança é "fazer o melhor esforço de limpeza depois que o pai sair e depois sair também". No entanto, eu realmente não tenho certeza sobre outras plataformas. O trecho C funciona no Linux e no FreeBSD, mas é tudo o que sei ... Além disso, há casos em que você deve tomar cuidado, como o pai forking novamente ou o pai desistindo do fd antes de sair verdadeiramente (criando assim uma janela de tempo para condição de corrida).
Cong Ma

@ Aktau - Isso será totalmente confiável.
Onívoro

1

Em POSIX , as funções exit(), _exit()e _Exit()são definidas para:

  • Se o processo for um processo de controle, o sinal SIGHUP deve ser enviado a cada processo no grupo de processos em primeiro plano do terminal de controle pertencente ao processo de chamada.

Portanto, se você solicitar que o processo pai seja um processo de controle para seu grupo de processos, o filho deverá receber um sinal SIGHUP quando o pai sair. Não tenho certeza absoluta de que isso acontece quando o pai falha, mas acho que sim. Certamente, para os casos sem travamento, deve funcionar bem.

Note que você pode ter que ler um monte de letras miúdas - incluindo a seção Definições Base (Definições), bem como as informações Serviços do Sistema para exit()e setsid()e setpgrp()- para obter a imagem completa. (Eu também!)


3
Hmm. A documentação é vaga e contraditória, mas parece que o processo pai deve ser o processo principal da sessão, não apenas o grupo de processos. O processo de liderança para a sessão era sempre o login, e fazer com que meu processo assumisse como processo de liderança para uma nova sessão estava além das minhas habilidades no momento.
Schof

2
O SIGHUP efetivamente só é enviado para processos filho se o processo de saída for um shell de logon. opengroup.org/onlinepubs/009695399/functions/exit.html "A finalização de um processo não finaliza diretamente seus filhos. O envio de um sinal SIGHUP conforme descrito abaixo finaliza indiretamente os filhos / em algumas circunstâncias /."
Rob K

1
@ Rob: correto - é o que diz também a citação: que apenas em algumas circunstâncias o processo filho recebe um SIGHUP. E é estritamente simplificado dizer que é apenas um shell de login que envia o SIGHUP, embora esse seja o caso mais comum. Se um processo com vários filhos se estabelecer como o processo de controle para si e para seus filhos, o SIGHUP (convenientemente) será enviado a seus filhos quando o mestre morrer. OTOH, os processos raramente dão tanto trabalho - então, eu sou mais exigente do que levantar uma queixa realmente significativa.
Jonathan Leffler

2
Eu brinquei com ele por algumas horas e não consegui fazê-lo funcionar. Teria lidado bem com um caso em que eu tenho um daemon com algumas crianças que precisam morrer quando os pais saem.
Rob K

1

Se você enviar um sinal para o pid 0, usando por exemplo

kill(0, 2); /* SIGINT */

esse sinal é enviado para todo o grupo de processos, matando efetivamente a criança.

Você pode testá-lo facilmente com algo como:

(cat && kill 0) | python

Se você pressionar ^ D, verá o texto "Terminated"como uma indicação de que o intérprete Python foi realmente morto, em vez de apenas sair por causa do fechamento do stdin.


1
(echo -e "print(2+2)\n" & kill 0) | sh -c "python -" felizmente imprime 4 em vez de Terminação
Kamil Szot

1

Caso seja relevante para qualquer outra pessoa, quando eu gerar instâncias da JVM em processos filho bifurcados a partir do C ++, a única maneira de conseguir que as instâncias da JVM sejam encerradas corretamente após o processo pai é fazer o seguinte. Espero que alguém possa fornecer feedback nos comentários se essa não foi a melhor maneira de fazer isso.

1) Chame prctl(PR_SET_PDEATHSIG, SIGHUP)o processo filho bifurcado, conforme sugerido antes de iniciar o aplicativo Java via execve

2) Adicione um gancho de desligamento ao aplicativo Java que pesquisa até que seu PID pai seja igual a 1 e faça um esforço Runtime.getRuntime().halt(0). A pesquisa é feita iniciando um shell separado que executa o pscomando (consulte: Como localizo meu PID em Java ou JRuby no Linux? ).

EDIT 130118:

Parece que não era uma solução robusta. Ainda estou lutando um pouco para entender as nuances do que está acontecendo, mas às vezes ainda estava obtendo processos órfãos da JVM ao executar esses aplicativos em sessões de tela / SSH.

Em vez de pesquisar o PPID no aplicativo Java, simplesmente solicitei que o gancho de desligamento realizasse a limpeza seguida de uma parada forçada, como acima. Em seguida, certifiquei-me de invocar waitpido aplicativo pai C ++ no processo filho gerado quando era hora de encerrar tudo. Essa parece ser uma solução mais robusta, pois o processo filho garante a finalização, enquanto o pai usa as referências existentes para garantir que seus filhos sejam finalizados. Compare isso com a solução anterior, que teve o processo pai finalizado sempre que quisesse, e os filhos tentaram descobrir se eles ficaram órfãos antes de terminar.


1
A PID equals 1espera não é válida. O novo pai pode ser outro PID. Você deve verificar se ele muda do pai original (getpid () antes da bifurcação ()) para o novo pai (getppid () na criança não é igual ao getpid () quando chamado antes da bifurcação ()).
Alexis Wilke

1

Outra maneira de fazer isso que é específico do Linux é fazer com que o pai seja criado em um novo espaço de nome do PID. Ele será o PID 1 nesse espaço de nome e, quando sair, todos os seus filhos serão imediatamente mortos SIGKILL.

Infelizmente, para criar um novo espaço para nome do PID, você precisa ter CAP_SYS_ADMIN. Mas, esse método é muito eficaz e não requer nenhuma alteração real nos pais ou nos filhos além do início do pai.

Veja clone (2) , pid_namespaces (7) e cancelamento de compartilhamento (2) .


Eu preciso editar de outra maneira. É possível usar o prctl para fazer com que um processo atue como o processo init para todos os seus filhos, netos e bisnetos, etc ...
Omnifarious 28/01

0

Se o pai morre, o PPID dos órfãos muda para 1 - você só precisa verificar o seu próprio PPID. De certa forma, isso é pesquisa, mencionado acima. aqui está uma peça de concha para isso:

check_parent () {
      parent=`ps -f|awk '$2=='$PID'{print $3 }'`
      echo "parent:$parent"
      let parent=$parent+0
      if [[ $parent -eq 1 ]]; then
        echo "parent is dead, exiting"
        exit;
      fi
}


PID=$$
cnt=0
while [[ 1 = 1 ]]; do
  check_parent
  ... something
done

0

Encontrei 2 soluções, ambas não perfeitas.

1. Mate todas as crianças matando (-pid) quando receber o sinal SIGTERM.
Obviamente, esta solução não pode lidar com "kill -9", mas funciona na maioria dos casos e é muito simples porque não precisa se lembrar de todos os processos filhos.


    var childProc = require('child_process').spawn('tail', ['-f', '/dev/null'], {stdio:'ignore'});

    var counter=0;
    setInterval(function(){
      console.log('c  '+(++counter));
    },1000);

    if (process.platform.slice(0,3) != 'win') {
      function killMeAndChildren() {
        /*
        * On Linux/Unix(Include Mac OS X), kill (-pid) will kill process group, usually
        * the process itself and children.
        * On Windows, an JOB object has been applied to current process and children,
        * so all children will be terminated if current process dies by anyway.
        */
        console.log('kill process group');
        process.kill(-process.pid, 'SIGKILL');
      }

      /*
      * When you use "kill pid_of_this_process", this callback will be called
      */
      process.on('SIGTERM', function(err){
        console.log('SIGTERM');
        killMeAndChildren();
      });
    }

Da mesma forma, você pode instalar o manipulador 'exit' como acima, se chamar process.exit em algum lugar. Nota: Ctrl + C e falha súbita foram processados ​​automaticamente pelo sistema operacional para eliminar o grupo de processos, portanto não há mais aqui.

2.Use chjj / pty.js para gerar seu processo com o terminal de controle conectado.
Quando você mata o processo atual mesmo matando -9, todos os processos filhos também são automaticamente mortos (pelo sistema operacional?). Eu acho que, como o processo atual fica do outro lado do terminal, se o processo atual morrer, o processo filho receberá o SIGPIPE.


    var pty = require('pty.js');

    //var term =
    pty.spawn('any_child_process', [/*any arguments*/], {
      name: 'xterm-color',
      cols: 80,
      rows: 30,
      cwd: process.cwd(),
      env: process.env
    });
    /*optionally you can install data handler
    term.on('data', function(data) {
      process.stdout.write(data);
    });
    term.write(.....);
    */

0

Consegui fazer uma solução portátil, sem polling, com três processos, abusando do controle e das sessões do terminal. Isso é masturbação mental, mas funciona.

O truque é:

  • processo A é iniciado
  • processo A cria um canal P (e nunca lê a partir dele)
  • processo A bifurca-se no processo B
  • processo B cria uma nova sessão
  • processo B aloca um terminal virtual para essa nova sessão
  • processo B instala o manipulador SIGCHLD para morrer quando a criança sai
  • o processo B define um manipulador SIGPIPE
  • o processo B bifurca-se no processo C
  • o processo C faz o que for necessário (por exemplo, exec () é o binário não modificado ou executa qualquer lógica)
  • o processo B grava no canal P (e bloqueia esse caminho)
  • processo A wait () s no processo B e sai quando morre

Dessa maneira:

  • se o processo A morre: o processo B recebe um SIGPIPE e morre
  • se o processo B morre: o processo wait () retorna e morre, o processo C recebe um SIGHUP (porque quando o líder da sessão com um terminal conectado morre, todos os processos no grupo de processos em primeiro plano recebem um SIGHUP)
  • se o processo C morre: o processo B recebe um SIGCHLD e morre, então o processo A morre

Deficiências:

  • processo C não pode lidar com SIGHUP
  • o processo C será executado em uma sessão diferente
  • o processo C não pode usar a API do grupo de sessões / processos porque isso quebrará a configuração frágil
  • criar um terminal para cada operação desse tipo não é a melhor idéia de todos os tempos

0

Mesmo depois de sete anos, acabei de encontrar esse problema, pois estou executando o aplicativo SpringBoot que precisa iniciar o webpack-dev-server durante o desenvolvimento e precisa eliminá-lo quando o processo de back-end parar.

Eu tento usar, Runtime.getRuntime().addShutdownHookmas funcionou no Windows 10, mas não no Windows 7.

Eu mudei para usar um segmento dedicado que aguarda o encerramento do processo ou InterruptedExceptionque parece funcionar corretamente nas duas versões do Windows.

private void startWebpackDevServer() {
    String cmd = isWindows() ? "cmd /c gradlew webPackStart" : "gradlew webPackStart";
    logger.info("webpack dev-server " + cmd);

    Thread thread = new Thread(() -> {

        ProcessBuilder pb = new ProcessBuilder(cmd.split(" "));
        pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
        pb.directory(new File("."));

        Process process = null;
        try {
            // Start the node process
            process = pb.start();

            // Wait for the node process to quit (blocking)
            process.waitFor();

            // Ensure the node process is killed
            process.destroyForcibly();
            System.setProperty(WEBPACK_SERVER_PROPERTY, "true");
        } catch (InterruptedException | IOException e) {
            // Ensure the node process is killed.
            // InterruptedException is thrown when the main process exit.
            logger.info("killing webpack dev-server", e);
            if (process != null) {
                process.destroyForcibly();
            }
        }

    });

    thread.start();
}

0

Historicamente, no UNIX v7, o sistema de processos detectou a órfã dos processos, verificando o ID pai de um processo. Como eu digo, historicamente, o init(8)processo do sistema é um processo especial por apenas uma razão: ele não pode morrer. Ele não pode morrer porque o algoritmo do kernel para lidar com a atribuição de um novo ID do processo pai depende desse fato. quando um processo executa sua exit(2)chamada (por meio de uma chamada do sistema de processo ou por uma tarefa externa, enviando um sinal ou algo parecido), o kernel reatribui a todos os filhos deste processo o ID do processo init como o ID do processo pai. Isso leva ao teste mais fácil e mais portátil de saber se um processo ficou órfão. Basta verificar o resultado da getppid(2)chamada do sistema e se é o ID do processo doinit(2) processo, o processo ficou órfão antes da chamada do sistema.

Dois problemas emergem dessa abordagem que podem levar a problemas:

  • primeiro, temos a possibilidade de alterar o initprocesso para qualquer processo do usuário; portanto, como podemos garantir que o processo init sempre seja o pai de todos os processos órfãos? Bem, no exitcódigo de chamada do sistema, há uma verificação explícita para ver se o processo que está executando a chamada é o processo init (o processo com pid igual a 1) e, nesse caso, o kernel entra em pânico (não deve mais ser capaz de manter hierarquia do processo), portanto, não é permitido que o processo init faça uma exit(2)chamada.
  • segundo, há uma condição de corrida no teste básico exposto acima. O id do processo inicial é assumido historicamente 1, mas isso não é garantido pela abordagem POSIX, que afirma (como exposto em outra resposta) que apenas o ID do processo de um sistema é reservado para esse fim. Quase nenhuma implementação posix faz isso, e você pode supor em sistemas derivados do unix originais que ter 1como resposta a getppid(2)chamada do sistema é suficiente para assumir que o processo é órfão. Outra maneira de verificar é fazer um getppid(2)logo após o garfo e comparar esse valor com o resultado de uma nova chamada. Isso simplesmente não funciona em todos os casos, pois as duas chamadas não são atômicas juntas e o processo pai pode morrer após a fork(2)e antes da primeira getppid(2)chamada do sistema. A parent id only changes once, when its parent does ansaída do processo (2) call, so this should be enough to check if thegetppid (2)result changed between calls to see that parent process has exit. This test is not valid for the actual children of the init process, because they are always children ofinit (8) `, mas você pode assumir com segurança esses processos como não tendo pai (exceto quando você substitui em um sistema o processo init)

-1

Passei o pai pid usando o ambiente para o filho e verifiquei periodicamente se / proc / $ ppid existe no filho.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.