Tubos com vazamento no linux

12

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

$ a | b

Se bparar de processar stdin, depois de um tempo, o tubo será preenchido e gravado, aaté o stdout, será bloqueado (até que o bprocesso comece novamente ou a matriz morra).

Se eu quisesse evitar isso, ficaria tentado a usar um tubo maior (ou, mais simplesmente buffer(1)) assim:

$ a | buffer | b

Isso simplesmente me daria mais tempo, mas no final aacabaria.

O que eu gostaria de ter (para um cenário muito específico que estou abordando) é ter um canal "vazado" que, quando cheio, soltaria alguns dados (idealmente, linha por linha) do buffer para permitir acontinuar processamento (como você provavelmente pode imaginar, os dados que fluem no canal são dispensáveis, ou seja, ter os dados processados ​​por bé menos importante do que apoder executar sem bloquear).

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

$ a | leakybuffer | b

Provavelmente eu poderia implementá-lo facilmente em qualquer idioma, estava me perguntando se há algo "pronto para usar" (ou algo como uma única linha de base) que estou perdendo.

Nota: nos exemplos, estou usando pipes regulares, mas a pergunta se aplica igualmente aos pipes nomeados


Embora tenha concedido a resposta abaixo, também decidi implementar o comando leakybuffer porque a solução simples abaixo tinha algumas limitações: https://github.com/CAFxX/leakybuffer

CAFxX
fonte
Os tubos nomeados realmente se enchem? Eu pensaria que pipes nomeados são a solução para isso, mas não posso dizer com certeza.
Curinga
3
Pipes nomeados têm (por padrão) a mesma capacidade como tubos sem nome, AFAIK
CAFxX

Respostas:

14

A maneira mais fácil seria canalizar através de algum programa que define a saída sem bloqueio. Aqui está um simples perl oneliner (que você pode salvar como leakybuffer ) que faz isso:

então você a | bse torna:

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

o que é que faz é ler a entrada e gravar na saída (igual a cat(1)), mas a saída é sem bloqueio - o que significa que, se a gravação falhar, retornará erro e perderá dados, mas o processo continuará com a próxima linha de entrada, pois ignoramos convenientemente erro. O processo é do tipo buffer de linha, como você deseja, mas veja a observação 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ê obterá o outputarquivo 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 as linhas depois 12773, mas também uma anomalia - o perl não tinha buffer suficiente para, 12774\nmas fez por 1277isso escreveu exatamente isso - e o próximo número 75610não inicia no início da linha, tornando-o pequeno feio.

Isso poderia ser melhorado com a detecção do perl quando a gravação não foi bem-sucedida e, posteriormente, tente liberar o restante da linha enquanto ignora as novas linhas que chegam, mas isso complicaria muito mais o script do perl, portanto é 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 um pouco o comando, ou o perl consumirá grandes quantidades de memória (dependendo da frequência com que os caracteres de nova linha aparecem em sua entrada):

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

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

Update2 - saída melhor do arquivo de texto: Evitando buffers de saída (em syswritevez 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 as linhas foram cortadas com: perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' outputoneliner)

Matija Nalis
fonte
Eu amo o oneliner: Não sou especialista perl, se alguém poderia sugerir as melhorias acima seria fantástico
CAFxX
1
Isso parece funcionar até certo ponto . Mas, enquanto observo meu comando perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_WRONLY|O_NONBLOCK; while (<STDIN>) { print }' | aplay -t raw -f dat --buffer-size=16000, o perl parece alocar continuamente mais memória até ser eliminada pelo gerente do OOM.
Ponkadoodle
@ Wallacoloo obrigado por apontar isso, meu caso estava transmitindo arquivos de log ... Consulte a resposta atualizada para uma pequena alteração necessária para oferecer suporte a arquivos binários.
Matija Nalis
Veja também GNU dd's dd oflag=nonblock status=none.
Stéphane Chazelas
1
Desculpe, meu erro de novo, na verdade, gravações com menos de bytes PIPE_BUF (4096 no Linux, necessários para ser pelo menos 512 pelo POSIX) são garantidas como atômicas, portanto, $| = 1sua syswrite()abordagem evita gravações curtas, na verdade, desde que as linhas sejam razoavelmente curtas.
Stéphane Chazelas