Por que o ps * very * ocasionalmente falhava em encontrar um processo válido?

9

Eu deparei com um problema estranho no qual um ps -o args -p <pid>comando falha ocasionalmente ao encontrar o processo em questão, mesmo que esteja definitivamente em execução no servidor em questão. Os processos em questão são scripts de wrapper de longa execução usados ​​para iniciar alguns aplicativos Java.

As ocorrências "in the wild" da questão sempre parece acontecer no início da manhã, para que haja alguma evidência de que ele está relacionado à carga em disco no servidor em questão, porque eles estão muito fortemente carregado então, mas executando o psem pergunta em um loop apertado, eu posso eventualmente replicar o problema - uma vez a cada poucas centenas de vezes que eu recebo um erro.

Ao executar o seguinte script bash, eu consegui gerar uma saída de rastreio para uma execução com falha e com êxito:

while [ $? == 0 ] ; do strace -o fail.out ps -o args -p <pid> >/dev/null ; done ; strace -o good.out ps -o args -p <pid>

Comparando a saída de fail.oute good.out, posso ver que a getdentschamada do sistema na execução que falha de alguma forma retorna um número muito menor do que a contagem real de processos no sistema (da ordem de ~ 500 em comparação com ~ 1100)

grep getdents good.out
  getdents(5, /* 1174 entries */, 32768)  = 32760
  getdents(5, /* 31 entries */, 32768)    = 992
  getdents(5, /* 0 entries */, 32768)     = 0

grep getdents fail.out
  getdents(5, /* 673 entries */, 32768)   = 16728
  getdents(5, /* 0 entries */, 32768)     = 0

... e essa lista mais curta não inclui o pid real em questão, portanto não foi encontrado.

Você pode ignorar esta seção, os erros ENOTTY são explicados pelo comentário de dave_thompson abaixo e não são relacionados

Além disso, a execução com falha obtém alguns ENOTTYerros que não aparecem na execução bem-sucedida. Perto do início da saída, vejo

ioctl (1, TIOCGWINSZ, 0x7fffe19db310) = -1 ENOTTY (ioctl inadequado para o dispositivo) ioctl (1, TCGETS, 0x7fffe19db280) = -1 ENOTTY (ioctl inadequado para o dispositivo)

E no final, vejo um único

ioctl (1, TCGETS, 0x7fffe19db0d0) = -1 ENOTTY (ioctl inadequado para o dispositivo)

A falha ioctlno final ocorre logo antes dos psretornos, mas ocorre após o psjá ter imprimido um conjunto de resultados vazio, portanto, não tenho certeza se eles estão relacionados. Eu sei que eles são consistentes em todas as saídas strace com falha que tenho, mas não aparecem nas que foram bem-sucedidas.

Não tenho absolutamente nenhuma idéia de por getdentsque ocasionalmente não encontraria a lista completa de processos, e agora cheguei ao ponto em que vou dar um tapa-band na coisa toda, alterando o script de controle que verifica o script do wrapper em questão para ligar pela pssegunda vez se o primeiro falhar, mas eu estaria interessado em saber se alguém tem alguma idéia do que está acontecendo aqui.

O sistema em questão está executando o Kernel 4.16.13-1.el7.elrepo.x86_64 no CentOS 7 e procps-ng versão 3.3.10-17.el7_5.2.x86_64

James
fonte
1
Para sua informação, os ioctls têm a ver com a obtenção de configurações de terminal (por exemplo, a primeira é encontrar o número de linhas e colunas) - por isso é estranho que estejam falhando, mas provavelmente não é uma causa direta. Isso soa como um bug do kernel ...
derobert
2
pesquisa relacionada ao OpenBSD: https://www.google.com.tedunangst.com/flak/post/…
thrig
2
Você tem >/dev/nullna invocação 'falha' (no circuito), mas não o 'bom' invocação, daí o ENOTTY em fd 1.
dave_thompson_085
Oh caramba. Obrigado por capturar aquele Dave, que certamente explica os ENOTTYs.
James
Fico feliz em ver que não sou o único com esse problema. A maneira que eu contornar esta situação está a ter um try-catch que tentará se o comando falhar, ainda irritante embora: /
Josh Correia

Respostas:

7

Considere ler as informações necessárias diretamente do /procsistema de arquivos e não através de uma ferramenta como ps. Você encontrará as informações que procura ("args") dentro do arquivo /proc/$pid/cmdline, separadas apenas por NUL bytes, em vez de espaços.

Você pode usar esta linha sedúnica para obter os argumentos do processo $pid:

sed -e 's/\x00\?$/\n/' -e 's/\x00/ /g' "/proc/$pid/cmdline"

Este comando é equivalente a:

ps -o args= -p "$pid"

(Usar args=em psomitirá o cabeçalho.)

O sedcomando primeiro procurará o último byte NUL à direita e o substituirá por uma nova linha; depois disso, substituirá todos os outros bytes NUL (separando argumentos individuais) por espaços, produzindo finalmente o mesmo formato do qual você está vendo ps.


Em relação aos processos de listagem no sistema, psfaça-o listando diretórios /proc, mas existem condições de corrida inerentes a esse procedimento, uma vez que os processos estão iniciando e saindo enquanto psestá em execução; portanto, o que você obtém não é realmente um instantâneo, mas uma aproximação. Em particular, é possível que psmostre processos que já foram finalizados no momento em que mostra seus resultados ou omita processos iniciados enquanto estava em execução (mas não foram retornados pelo kernel ao listar o conteúdo de /proc.)

Eu sempre assumi que, se um processo existe antes do psinício e ainda existe após a conclusão, não seria esquecido, assumi que o kernel garantiria que eles sempre fossem incluídos, mesmo que haja muitos outros processos. sendo criado e destruído. O que você está descrevendo implica que não é esse o caso. Ainda estou cético quanto a isso, mas, como existem condições de corrida conhecidas em como psfunciona, acho que é pelo menos plausível que listar PIDs /procpossa perder uma existente devido a essas condições de corrida.

Seria possível verificar isso verificando a fonte do kernel do Linux, mas ainda não o fiz (ainda), então não posso realmente ter certeza se existe uma condição de corrida que perderia um processo de longa execução, como você descreve.


A outra parte é a maneira como psfunciona. Mesmo se você estiver passando um único PID com o -pargumento, ele ainda está listando todos os PIDs existentes, mesmo que você esteja interessado apenas nesse único. Definitivamente, poderia pegar um atalho nesse caso e pular a lista das entradas /proce ir diretamente para /proc/$pid.

Não sei dizer por que foi implementado dessa maneira. Talvez porque a maioria das psopções seja "filtros" nos processos, a implementação -pda mesma maneira fosse mais fácil, usando um atalho para ir direto para /proc/$pidenvolver um caminho de código separado ou duplicação de código ... Outra hipótese é que alguns casos, incluindo -popções adicionais, seriam acabam exigindo listagem, por isso talvez seja complexo determinar quais casos exatos permitiriam o atalho e quais não.


O que nos leva à solução alternativa, indo direto para /proc/$pid, sem listar o conjunto completo de PIDs do sistema, evitando todas as corridas conhecidas e simplesmente obtendo as informações necessárias diretamente da fonte.

É um pouco feio, mas o problema que você descreve realmente existe, deve ser uma maneira confiável de obter essas informações.

filbranden
fonte
2
Obrigado por esse Filipe, eu votei porque o comando sed é útil (e mudei nossos scripts para apenas procurar em / proc) e porque não percebi que adicionar um '=' ao ps derrubaria o cabeçalho . Eu não aceito a resposta, porque eu ainda estou muito curioso para saber por que ele não ver toda a lista de / proc e eu estou mantendo a esperança alguém sabe :)
James