Linux中的“泄漏”管道


12

假设您具有如下所示的管道:

$ a | b

如果b停止处理stdin,则在一段时间后,管道将被填满,并将a对其的stdout 写入,将阻塞(直到b再次开始处理或终止)。

如果我想避免这种情况,可以尝试使用更大的管道(或更简单地说,是buffer(1)),如下所示:

$ a | buffer | b

这只会给我带来更多的时间,但a最终最终会停止。

我很想拥有(对于我要解决的非常特殊的情况)是拥有一个“泄漏的”管道,该管道在充满时会从缓冲区中丢弃一些数据(理想的是逐行)以a继续处理(您可能会想到,在管道中流动的数据是消耗性的,即,处理数据的b重要性不如a能够不阻塞地运行)。

总结一下,我很想拥有一个有界,漏水的缓冲区:

$ a | leakybuffer | b

我可能可以很容易地用任何一种语言来实现它,我只是想知道我是否缺少某些“准备使用”的东西(或者像bash单线的东西)。

注意:在示例中,我使用的是常规管道,但问题同样适用于命名管道


在授予以下答案的同时,我也决定实施LeakyBuffer命令,因为以下简单解决方案有一些局限性:https : //github.com/CAFxX/leakybuffer


命名管道真的填满了吗?我本以为命名管道解决此问题的方法,但我不能肯定地说。
通配符

3
(默认情况下)命名管道与未命名管道AFAIK
CAFxX的

Answers:


14

最简单的方法是通过管道访问设置无阻塞输出的程序。这是简单的perl oneliner(您可以将其保存为泄漏缓冲区),它可以这样做:

所以你a | b变成:

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

所做的是读取输入并写入输出(与相同cat(1)),但是输出是非阻塞的-这意味着如果写入失败,它将返回错误并丢失数据,但是该过程将继续输入的下一行,因为我们方便地忽略了输入错误。流程可以根据需要进行行缓冲,但是请参见下面的警告。

您可以使用以下示例进行测试:

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

您将获得output丢失行的文件(确切的输出取决于外壳的速度等),如下所示:

12768
12769
12770
12771
12772
12773
127775610
75611
75612
75613

您会看到shell在哪里丢失了行12773,但是又出现了一个异常-perl没有足够的缓冲区,12774\n但是这样做了,1277所以它只写了这样-因此下一个数字75610不在行的开头开始,因此很少丑陋。

可以通过以下方式来改进:通过perl检测何时写入未完全成功,然后在忽略新行的同时刷新剩余的行,但这会使perl脚本更加复杂,因此作为练习有兴趣的读者:)

更新(针对二进制文件): 如果您不处理换行符终止的行(如日志文件或类似文件),则需要稍微更改命令,否则perl将消耗大量内存(取决于换行符在输入中出现的频率):

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

它也将对二进制文件也正常工作(不消耗额外的内存)。

Update2-更好的文本文件输出: 避免使用输出缓冲区(syswrite而不是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

似乎为我解决了“合并线”的问题:

12766
12767
12768
16384
16385
16386

(注:可以使用perl -ne '$c++; next if $c==$_; print "$c $_"; $c=$_' outputoneliner 验证在哪些行上剪切了输出)


我喜欢oneliner:我不是perl专家,如果有人可以建议上面的改进会很棒
CAFxX

1
这似乎在一定程度上起作用。但是,当我看到命令时perl -w -MFcntl -e 'fcntl STDOUT,F_SETFL,O_WRONLY|O_NONBLOCK; while (<STDIN>) { print }' | aplay -t raw -f dat --buffer-size=16000,perl似乎会不断分配更多内存,直到被OOM管理器杀死为止。
Ponkadoodle

@Wallacoloo感谢您指出,我的情况是流式日志文件...有关支持二进制文件所需的微小更改,请参见更新的答案。
Matija Nalis

另请参见GNU dddd oflag=nonblock status=none
斯特凡Chazelas

1
抱歉,再次不好意思,实际上保证少于PIPE_BUF字节(Linux上为4096,POSIX要求至少为512)的写入是原子的,因此$| = 1syswrite()只要行相当短,您的方法的确会防止短写入。
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.