Processo que não fecha quando stdin é fechado

4

Estou iniciando um processo com um programa. Espero que o processo termine quando o programa o fizer, pois ele perde seu stdin.

Eu encerrei o programa, depois fui para o proc / pid / fd para o processo e descobri que o seu stdin ainda está vinculado ao / dev / pts / 2.

Por que o processo não é encerrado neste caso? Melhor ainda, existe um wrapper ou técnica que eu possa usar para garantir que o programa será fechado quando o stdin pipe se fechar?

    
por Turtle V. Rabbit 24.02.2016 / 11:41

1 resposta

19

stdin é descritor de arquivo 0 . Fechar um descritor de arquivo de um processo é algo que só pode ser feito ativamente pelo próprio processo. stdin é fechado quando o processo decide fechar o período.

Agora, quando o stdin de um processo é o final de leitura de um pipe, a outra extremidade do pipe pode ser aberta por um ou mais outros processos. Quando todos os descritores de arquivo para o outro lado tiverem sido fechados, a leitura desse pipe lerá os dados restantes ainda nesse pipe, mas acabará retornando nada (em vez de esperar por mais dados) significando fim de arquivo. / p>

Aplicativos como cat , cut , wc ... que são lidos de seus stdin geralmente saem quando isso acontece porque o papel deles é processar suas entradas até o fim até que não haja mais entrada.

Não há nenhum mecanismo mágico que faça com que os aplicativos morram quando o final de sua entrada é atingido, apenas eles decidem sair quando isso acontece.

Em:

echo foo | cat

Uma vez que echo tenha escrito "foo\n" , sai o que faz com que a extremidade de gravação do canal seja fechada, então o read() feito por cat na outra extremidade retorna 0 bytes, o que informa cat não há mais nada para ler e, em seguida, cat decide sair.

Em

echo foo | sleep 1

sleep só sai depois de 1 segundo. Seu stdin se tornando um tubo fechado não tem nenhuma incidência nisso, sleep não está nem lendo seu stdin.

É diferente no final da escrita dos pipes (ou soquetes para isso).

Quando todos os fds no final da leitura foram fechados, qualquer tentativa de escrever nos fds abertos para o fim da escrita faz com que um SIGPIPE seja enviado para o processo fazendo com que ele morra (a menos que ele ignore o sinal, caso em que o write() falha com EPIPE ).

Mas isso só acontece quando eles tentam escrever.

Por exemplo, em:

sleep 1 | true

Mesmo que true saia imediatamente e o final da leitura seja encerrado imediatamente, sleep não é eliminado porque não tenta gravar em seu stdout.

Agora, sobre /proc/fd/pid/n em vermelho na saída ls -l --color (conforme mencionado na primeira versão da sua pergunta ), isso é apenas porque ls faz um lstat() no resultado de readlink() nesse link simbólico para tentar determinar o tipo de destino do link.

Para descritores de arquivos abertos em pipes, ou sockets ou arquivos em outros namespaces ou arquivos excluídos, o resultado de readlink não será um caminho real no sistema de arquivos, portanto, o segundo lstat() feito por ls falhará e ls vai achar que é um link simbólico quebrado, e links simbólicos quebrados são renderizados em vermelho. Você conseguirá isso com qualquer fd para qualquer extremidade de qualquer tubo, seja a outra extremidade do tubo fechada ou não. Tente com ls --color=always -l /proc/self/fd | cat , por exemplo.

Para determinar se um fd aponta para um pipe quebrado, no Linux, você pode tentar lsof com a opção -E .

$ exec 3> >(:) 4> >(sleep 999)
$ lsof -ad3-4 -Ep "$$"
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
zsh     13155 stephane    3w  FIFO   0,10      0t0 5322414 pipe
zsh     13155 stephane    4w  FIFO   0,10      0t0 5323312 pipe 392,sleep,0r

Para o fd 3, o lsof não conseguiu encontrar nenhum outro processo na extremidade de leitura do pipe. Cuidado, você pode obter resultados como:

$ exec 5<&3
$ lsof -ad3-5 -Ep "$$"
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
zsh     13155 stephane    3w  FIFO   0,10      0t0 5322414 pipe 13155,zsh,5w
zsh     13155 stephane    4w  FIFO   0,10      0t0 5323312 pipe 392,sleep,0r
zsh     13155 stephane    5w  FIFO   0,10      0t0 5322414 pipe 392,sleep,3w 13155,zsh,3w

os fds 3 e 5 ainda estão em pipes quebrados, porque não há fd no final da leitura (parece haver um bug no lsof, já que o fato de sleep também ter seu fd 3 aberto para o cano quebrado não é refletido em todos os lugares).

Para matar um processo assim que o canal aberto em seu stdin perde seu último gravador (fica quebrado), você poderia fazer algo como:

run_under_watch() {
  perl -MIO::Poll -e '
     if ($pid = fork) {
       $SIG{CHLD} = sub {
         wait;
         exit($? & 127 ? ($? & 127) + 128 : $? >> 8);
       };
       $p = IO::Poll->new; $p->mask(STDIN, POLLERR); $p->poll;
       kill "TERM", $pid;
       sleep 1;
       kill "KILL", $pid;
       exit(1);
     } else {
       exec @ARGV
     }' "$@"
 }

O qual procuraria por uma condição de erro no stdin (e no Linux, isso parece acontecer assim que não houver mais nenhum gravador, mesmo se houver dados no pipe) e matar o comando filho assim que isso acontecer. Por exemplo:

 sleep 1 | run_under_watch sleep 2

Mataria o processo sleep 2 após 1 segundo.

Agora, geralmente isso é meio bobagem de se fazer. Isso significa que você está potencialmente matando um comando antes de ter tempo de processar o final de sua entrada. Por exemplo, em:

 echo test | run_under_watch cat

Você verá que cat às vezes é eliminado antes de ter tido tempo de saída (ou mesmo de leitura!) "test\n" . Não há como evitar isso, nosso watcher não sabe quanto tempo o comando precisa para processar a entrada. Tudo o que podemos fazer é dar um período de carência antes do kill "TERM" esperando que o comando leia o conteúdo deixado no pipe e faça o que ele precisa fazer com ele.

    
por 24.02.2016 / 12:14

Tags