Perl 在打开 gzip 文件时给出“gzip:stdout:Broken pipe”错误,但前提是连接到数据库

Posted

技术标签:

【中文标题】Perl 在打开 gzip 文件时给出“gzip:stdout:Broken pipe”错误,但前提是连接到数据库【英文标题】:Perl gives "gzip: stdout: Broken pipe" error when opening gzipped files, but only if connecting to a DB 【发布时间】:2019-12-28 17:38:33 【问题描述】:

考虑以下在 Linux 机器上运行的程序,它会打开一个 gzip 压缩的输入文件:

#!/usr/bin/env perl

open (my $fileHandle, "-|", "/bin/zcat $ARGV[0]");
my $ff = <$fileHandle>;
close($fileHandle);

按预期工作(它什么也不做,但不打印错误):

$ bar.pl file.gz
$

现在,如果我使用相同的代码但之前连接到 mysql 数据库,gzip 会报错(您可以直接运行代码,这是一个开放的数据库,并且凭据可以工作):

#!/usr/bin/env perl
use DBI;
use strict;
use warnings;

my $dsn = "DBI:mysql:database=hg19;host=genome-mysql.cse.ucsc.edu";
my $db =  DBI->connect($dsn, 'genomep', 'password');
my $dbResults = $db->prepare("show tables");
my $ret = $dbResults->execute();
$dbResults->finish();
$db->disconnect();

open (my $fileHandle, "-|", "/bin/zcat $ARGV[0]");
my $ff = <$fileHandle>;
close($ff);

运行上面给出:

$ foo.pl file.gz 

gzip: stdout: Broken pipe

这显然是一个更复杂的程序的一部分,但我已设法将其精简为重现该问题的愚蠢的 sn-p。

发生了什么事?为什么连接到数据库会影响 gzip 的行为?请注意,一切似乎都正常(在实际程序中,我对 gzip 压缩的数据做了一些有用的事情),但为什么我会收到该错误消息?


事实证明,这种行为特定于(稍微)旧版本的 Perl 和/或 DBI。在失败的机器上,我有:

Ubuntu 为 x86_64-linux-gnu-thread-multi 构建的 Perl 5,版本 22,subversion 1 (v5.22.1) DBI 1.634 DBD 4.033 gzip 1.6

但是,在另外两台机器上它确实有效。这些有:

Ubuntu 为 x86_64-linux-gnu-thread-multi 构建的 Perl 5,版本 26,subversion 1 (v5.26.1) DBI 1.640 DBD 4.033 gzip 1.6

Arch Linux 为 x86_64-linux-thread-multi 构建的 Perl 5,版本 30,subversion 0 (v5.30.0) DBI 1.642 DBD 4.050 gzip 1.10

【问题讨论】:

请添加use autodie; 以检查失败的系统调用,它会显示更多信息。 两种变体对我来说都很好(在 Fedora 29 上)。 @StephenKitt 哇哦。是的,在我的 Arch 系统 (perl 5, version 30, subversion 0 (v5.30.0)) 和带有 perl 5, version 26, subversion 1 (v5.26.1) 的 Ubuntu 系统上都可以正常工作,但在两台带有 (perl 5, version 22, subversion 1 (v5.22.1) 的 Ubuntu 机器上都失败了。所以我猜这是一个奇怪的错误,后来已经修复. 谢谢! (可能)不相关:还请注意,您正在打开 $filehandle,但正在关闭 $ff。在第一段代码中,$filehandle 前面也没有my 不,这不是 Perl 版本。如上所述对我来说失败了。 perl 5.30.0 DBI 1.642 DBD::mysql 4.050 libmariadb-devel 3.1.2 gzip 1.10 【参考方案1】:

至少在这里,MySQL 库(可能)似乎正在屏蔽(忽略)SIGPIPE,这就是您所看到的。比较 strace 的输出,我在 MySQL 运行中看到这样一行:

rt_sigaction(SIGPIPE, sa_handler=SIG_IGN, sa_mask=[PIPE], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7f78bdf16840, sa_handler=SIG_DFL, sa_mask=[], sa_flags=0, 8) = 0

事实证明,您可以在不使用 MySQL 的情况下轻松复制该行为:

$SIGPIPE = 'IGNORE';

open (my $fileHandle, "-|", "/bin/zcat $ARGV[0]");
my $ff = <$fileHandle>;
close($ff);

或者,或者,您可以将信号重置为默认处理程序以使消息消失,即使在连接到 MySQL 之后,通过将其设置为 DEFAULT 而不是 IGNORE

顺便说一下,这是documented behavior of the MySQL library:

为避免在连接终止时中止程序,MySQL 在第一次调用 mysql_library_init()、mysql_init() 或 mysql_connect() 时阻止 SIGPIPE。

(也可能取决于 gzip 版本;可能某些版本的 gzip 在 init 上设置了信号处理程序。)

最终,您看到的是,如果 gzip 获得 SIGPIPE,它就会退出。如果它从 write 中返回错误(因为 SIGPIPE 被忽略),它会打印一条错误消息。

【讨论】:

在我的 Arch 上(有关版本,请参阅问题)即使使用 $SIGPIPE = 'IGNORE';,我也无法重现它。然而,在失败的机器上,$SIGPIPE = 'IGNORE'; 确实足以重现。所以,鉴于 gzip 版本是相同的,我可以在没有 DBI 代码的情况下重现,看起来它确实是一个 perl 版本的东西。【参考方案2】:

很可能发生了以下情况: gzip 尝试写入管道,您这边的程序没有读取到 eof,关闭管道。然后 Gzip 收到一个 SIGPIPE,并死于此错误消息。你能确认这正在发生吗?

【讨论】:

但是为什么只有在使用不相关的 DBI 代码运行时才会出现问题,而没有它也能正常工作呢? 谢谢,但事实证明这是我使用的版本中的一个错误,现已修复。哦! 很高兴知道。您能否添加一些有关不良版本的信息,以免其他人对此感到疑惑?

以上是关于Perl 在打开 gzip 文件时给出“gzip:stdout:Broken pipe”错误,但前提是连接到数据库的主要内容,如果未能解决你的问题,请参考以下文章

Perl | Perl读取gzip压缩文件

使用管道在 Perl 中将管道文件输出到 gzip 的 Python 等效项

Bash 脚本:给出错误:gzip:<文件>.conf.gz:文件意外结束

PHP打开gzip压缩的XML

无法打开/解压缩 xml.gzip 或 zip.gzip 文件

在 python Apache Beam 中打开一个 gzip 文件