“Leaky” pipes no linux

11

Vamos supor que você tenha um pipeline como o seguinte:

$ a | b

Se b parar o processamento de stdin, depois de um tempo o encanamento será preenchido e gravará, de a em seu stdout, será bloqueado (até que b inicie o processamento novamente ou morra).

Se eu quisesse evitar isso, poderia ficar tentado a usar um pipe maior (ou, mais simplesmente, buffer(1) ) da seguinte forma:

$ a | buffer | b

Isso simplesmente me daria mais tempo, mas no final a acabaria parando.

O que eu adoraria ter (para um cenário muito específico que estou abordando) é ter um canal "com vazamento" que, quando cheio, soltaria alguns dados (idealmente, linha por linha) do buffer para deixar a continuar processando (como você pode imaginar, os dados que fluem no canal são dispensáveis, ou seja, ter os dados processados por b é menos importante do que ter a capaz de executar sem bloquear).

Para resumir, eu adoraria ter algo como um buffer limitado e com vazamentos:

$ a | leakybuffer | b

Eu provavelmente poderia implementá-lo facilmente em qualquer idioma, só queria saber se há algo "pronto para usar" (ou algo parecido com uma frase de uma frase) que estou perdendo.

Nota: nos exemplos estou usando pipes regulares, mas a questão se aplica igualmente a pipes nomeados

Enquanto eu concedi a resposta abaixo, eu também decidi implementar o comando leakybuffer porque a solução simples abaixo tinha algumas limitações: link

    
por CAFxX 10.08.2016 / 04:16

1 resposta

13

O caminho mais fácil seria passar por algum programa que define saída sem bloqueio. Aqui está um onliner perl simples (que você pode salvar como leakybuffer ) que faz isso:

, então seu a | b se torna:

a | perl -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | b

o que é é ler a entrada e gravar na saída (igual a cat(1) ), mas a saída não é bloqueada - o que significa que se a gravação falhar, retornará erro e perderá dados, mas o processo continuará com a próxima linha entrada como convenientemente ignorar o erro. O processo é do tipo line-buffered como você queria, mas veja a advertência abaixo.

você pode testar com, por exemplo:

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { print }' | \
    while read a; do echo $a; done > output

você receberá o arquivo output com linhas perdidas (a saída exata depende da velocidade do seu shell, etc.) assim:

12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613

você vê onde o shell perdeu linhas depois de 12773 , mas também uma anomalia - o perl não tinha buffer suficiente para 12774\n , mas sim para 1277 , por isso escreveu exatamente isso - e então o próximo número 75610 não inicia no início da linha, tornando-a pouco feia.

Isso pode ser melhorado com a detecção de perl quando a gravação não foi bem-sucedida, e depois tentar liberar o restante da linha ignorando novas linhas, mas isso complicaria muito mais o script perl, então é deixado como um exercício para o leitor interessado:)

Atualização (para arquivos binários): Se você não estiver processando linhas terminadas de nova linha (como arquivos de log ou similares), precisará alterar o comando ligeiramente, ou o perl consumirá grandes quantidades de memória (dependendo da frequência com que caracteres de nova linha aparecem em sua entrada):

perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (read STDIN, $_, 4096) { print }' 

funcionará corretamente também para arquivos binários (sem consumir memória extra).

Update2 - melhor saída de arquivo de texto: Evitando buffers de saída ( syswrite em vez de print ):

seq 1 500000 | perl -w -MFcntl -e \
    'fcntl STDOUT,F_SETFL,O_NONBLOCK; while (<STDIN>) { syswrite STDOUT,$_ }' | \
    while read a; do echo $a; done > output

parece corrigir problemas com "linhas mescladas" para mim:

12766
12767
12768
16384
16385
16386

(Nota: pode-se verificar em quais linhas a saída foi cortada com: perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' output oneliner)

    
por 11.08.2016 / 03:21