我有一个使用AnyEvent频繁写入文件的脚本。我写了下面的例子来说明我面临的问题。
#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent;
use AnyEvent::Handle;
my $outputFile = 'out_test.log';
open my $out, ">>", $outputFile or die "Can't open output\n";
my $data = "test string"x50000 . "\n";
my $out_ready = AnyEvent->condvar;
my $out_hdl; $out_hdl = AnyEvent::Handle->new(
fh => $out,
on_error => sub {
my ($hdl, $fatal, $msg) = @_;
AE::log error => $msg;
$hdl->destroy;
$out_ready->send;
}
);
my $timer = AnyEvent->timer(
after => 0,
interval => 5,
cb => sub {
$out_hdl->push_write($data);
}
);
$out_ready->recv;
这很好用,但一段时间后文件大小变得很大。我们使用logrotate来解决这个问题,所以我创建了以下logrotate配置文件。
/path/to/out_test.log {
size 2M
copytruncate
rotate 4
}
这也很有效,只要上述输出文件超过2M,它就会旋转到out_test.log.1。但是,在旋转后立即写入out_test.log时,文件大小与旋转的日志文件相同。我在这里解释了这种行为和我遇到的问题:https://serverfault.com/a/221343
虽然我理解了这个问题,但我不知道如何解决我提供的示例Perl代码中的问题。
我不必通过logrotate实现日志轮换,但它是首选。如果在脚本中实现起来很简单,我可以这样做,但如果我能使上面的示例与logrotate一起使用会很好。任何帮助或意见表示赞赏。谢谢!
修改
根据下面的答案,我能够使用monkeypatch ikegami提供的东西以及根据Marc Lehmann的建议利用本地perl I / O.我的示例代码看起来像这样,效果很好。此外,这还删除了logrotate中对copytruncate指令的要求。
#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent;
use AnyEvent::Handle;
my $outputFile = 'out_test.log';
open my $out, ">>", $outputFile or die "Can't open output\n";
my $data = "test string"x50000 . "\n";
my $cv = AnyEvent::condvar();
my $timer = AnyEvent->timer(
after => 0,
interval => 5,
cb => sub {
open my $out, ">>", $outputFile or die "Can't open output\n";
print $out $data;
close $out;
}
);
$cv->recv;
答案 0 :(得分:2)
通常,写入为追加句柄打开的句柄首先寻找文件的末尾。
如果文件是
open
(2)ed withO_APPEND
,则在写入之前首先将文件偏移设置为文件的末尾。文件偏移和写入操作的调整是作为原子步骤执行的。
但你没有看到AnyEvent :: Handle。以下说明了问题:
$ perl -e'
use strict;
use warnings;
use AE qw( );
use AnyEvent::Handle qw( );
sub wait_for_drain {
my ($hdl) = @_;
my $drained = AE::cv();
$hdl->on_drain($drained);
$drained->recv();
}
my $qfn = "log";
unlink($qfn);
open(my $fh, ">>", $qfn) or die $!;
$fh->autoflush(1);
my $hdl = AnyEvent::Handle->new(
fh => $fh,
on_error => sub {
my ($hdl, $fatal, $msg) = @_;
if ($fatal) { die($msg); } else { warn($msg); }
},
);
$hdl->push_write("abc\n");
$hdl->push_write("def\n");
wait_for_drain($hdl);
print(-s $qfn, "\n");
truncate($qfn, 0);
print(-s $qfn, "\n");
$hdl->push_write("ghi\n");
wait_for_drain($hdl);
print(-s $qfn, "\n");
'
8
0
12
虽然以下说明了您应该看到的行为:
$ perl -e'
use strict;
use warnings;
my $qfn = "log";
unlink($qfn);
open(my $fh, ">>", $qfn) or die $!;
$fh->autoflush(1);
print($fh "abc\n");
print($fh "def\n");
print(-s $qfn, "\n");
truncate($qfn, 0);
print(-s $qfn, "\n");
print($fh "ghi\n");
print(-s $qfn, "\n");
'
8
0
4
问题是AnyEvent :: Handle破坏了一些句柄的标志。上面的AnyEvent代码归结为以下内容:
$ perl -e'
use strict;
use warnings;
use Fcntl qw( F_SETFL O_NONBLOCK );
my $qfn = "log";
unlink($qfn);
open(my $fh, ">>", $qfn) or die $!;
$fh->autoflush(1);
fcntl($fh, F_SETFL, O_NONBLOCK);
print($fh "abc\n");
print($fh "def\n");
print(-s $qfn, "\n");
truncate($qfn, 0);
print(-s $qfn, "\n");
print($fh "ghi\n");
print(-s $qfn, "\n");
'
8
0
12
以下是AnyEvent :: Handle应该做的事情:
$ perl -e'
use strict;
use warnings;
use Fcntl qw( F_GETFL F_SETFL O_NONBLOCK );
my $qfn = "log";
unlink($qfn);
open(my $fh, ">>", $qfn) or die $!;
$fh->autoflush(1);
my $flags = fcntl($fh, F_GETFL, 0)
or die($!);
fcntl($fh, F_SETFL, $flags | O_NONBLOCK)
or die($!);
print($fh "abc\n");
print($fh "def\n");
print(-s $qfn, "\n");
truncate($qfn, 0);
print(-s $qfn, "\n");
print($fh "ghi\n");
print(-s $qfn, "\n");
'
8
0
4
我已经提交了一个错误报告,但该模块的作者不愿意修复该错误,所以我不得不推荐猴子修补的相当糟糕的做法。将以下内容添加到您的程序中:
use AnyEvent qw( );
use AnyEvent::Util qw( );
use Fcntl qw( );
BEGIN {
if (!AnyEvent::WIN32) {
my $fixed_fh_nonblocking = sub($$) {
my $flags = fcntl($_[0], Fcntl::F_GETFL, 0)
or return;
$flags = $_[1]
? $flags | AnyEvent::O_NONBLOCK
: $flags & ~AnyEvent::O_NONBLOCK;
fcntl($_[0], AnyEvent::F_SETFL, $flags);
};
no warnings "redefine";
*AnyEvent::Util::fh_nonblocking = $fixed_fh_nonblocking;
}
}
使用此修复程序,您的程序将正常运行
$ perl -e'
use strict;
use warnings;
use AE qw( );
use AnyEvent qw( );
use AnyEvent::Handle qw( );
use AnyEvent::Util qw( );
use Fcntl qw( );
BEGIN {
if (!AnyEvent::WIN32) {
my $fixed_fh_nonblocking = sub($$) {
my $flags = fcntl($_[0], Fcntl::F_GETFL, 0)
or return;
$flags = $_[1]
? $flags | AnyEvent::O_NONBLOCK
: $flags & ~AnyEvent::O_NONBLOCK;
fcntl($_[0], AnyEvent::F_SETFL, $flags);
};
no warnings "redefine";
*AnyEvent::Util::fh_nonblocking = $fixed_fh_nonblocking;
}
}
sub wait_for_drain {
my ($hdl) = @_;
my $drained = AE::cv();
$hdl->on_drain($drained);
$drained->recv();
}
my $qfn = "log";
unlink($qfn);
open(my $fh, ">>", $qfn) or die $!;
$fh->autoflush(1);
my $hdl = AnyEvent::Handle->new(
fh => $fh,
on_error => sub {
my ($hdl, $fatal, $msg) = @_;
if ($fatal) { die($msg); } else { warn($msg); }
},
);
$hdl->push_write("abc\n");
$hdl->push_write("def\n");
wait_for_drain($hdl);
print(-s $qfn, "\n");
truncate($qfn, 0);
print(-s $qfn, "\n");
$hdl->push_write("ghi\n");
wait_for_drain($hdl);
print(-s $qfn, "\n");
'
8
0
4
答案 1 :(得分:2)
ikegamis的答案非常具有误导性 - 您的代码包含一个错误,即使用AnyEvent :: Handle进行文件I / O,这是未记录和不受支持的行为。 ikegami通过在非法文件句柄上使用AnyEvent :: Handle来感知“bug”。
虽然你可以尝试依赖未记录的行为和monkeypatch的东西,并希望它会神奇地工作,只要你使用AnyEvent :: Handle非流文件句柄,你可能会遇到问题,所以我会考虑修复实际的错误。
如果你想做基于事件的文件I / O,那么你应该研究AnyEvent :: IO(并安装一个合适的后端,如IO :: AIO)。否则,您应该使用普通的perl I / O函数(内置函数,IO ::类等)来访问文件。
更新:AnyEvent :: Handle无法处理文件的深层原因是最终没有意义,因为非阻塞I / O的概念不适用于文件,因此使用AnyEvent :: Handle只会增加开销。