更新此问题可以使用https://github.com/zbentley/AnyEvent-Impl-Perl-Improved/tree/io-starvation中的修复程序来解决
语境:
我正在将 AnyEvent 与一些其他同步代码集成。同步代码需要安装一些观察者(在计时器、子进程和文件上),等待至少一个观察者完成,做一些同步/阻塞/遗留的事情,然后重复。
我正在使用基于纯 perlAnyEvent::Loop
的事件循环,这对于我的目的来说已经足够了;我需要它的大部分是信号/过程/定时器跟踪。
问题:
如果我有一个可以暂时阻止事件循环的回调,则子进程退出事件/回调永远不会触发。最简单的例子,我可以让手表成为一个子进程并运行一个间隔计时器。间隔计时器在完成之前会做一些阻塞:
use AnyEvent;
# Start a timer that, every 0.5 seconds, sleeps for 1 second, then prints "timer":
my $w2 = AnyEvent->timer(
after => 0,
interval => 0.5,
cb => sub {
sleep 1; # Simulated blocking operation. If this is removed, everything works.
say "timer";
},
);
# Fork off a pid that waits for 1 second and then exits:
my $pid = fork();
if ( $pid == 0 ) {
sleep 1;
exit;
}
# Print "child" when the child process exits:
my $w1 = AnyEvent->child(
pid => $pid,
cb => sub {
say "child";
},
);
AnyEvent->condvar->recv;
这段代码让子进程变得僵化,并一遍又一遍地打印“计时器”,“永远”(我运行了几分钟)。如果sleep 1
从计时器的回调中删除调用,则代码可以正常工作,并且子进程观察程序会按预期触发。
我希望孩子观察者最终会运行(在孩子退出后的某个时刻,事件队列中的任何间隔事件都会运行、阻塞和完成),但事实并非如此。
sleep 1
可以是任何阻塞操作。它可以用忙碌等待或任何其他需要足够长时间的东西来代替。它甚至不需要花一秒钟;它似乎只需要 a) 在子退出事件/SIGCHLD 传递期间运行,并且 b) 导致间隔始终要根据挂钟运行。
问题:
为什么 AnyEvent 从来没有运行我的子进程观察者回调?
如何将子进程退出事件与可能阻塞很长时间以致下一个间隔到期的间隔事件多路复用?
我试过的:
我的理论是,由于在事件循环之外花费的时间而变得“就绪”的计时器事件可以无限期地抢占 AnyEvent 内某处的其他类型的就绪事件(如子进程观察者)。我尝试了几件事:
- 使用
AnyEvent::Strict
不会以任何方式显示任何错误或改变行为。 - 部分解决方案:在任何时候删除间隔事件确实会使子进程观察程序触发(好像在 AnyEvent 内部完成了一些内部事件轮询/队列填充,只有当没有计时器事件根据挂钟“准备好”时才会发生)。缺点:在一般情况下不起作用,因为我必须知道我的子进程何时退出才能知道何时推迟我的间隔,这是重言式的。
- 部分解决方案:与子进程观察者不同,其他间隔计时器似乎能够相互多路复用,所以我可以
waitpid
在另一个间隔计时器中安装手动调用来检查和获取子进程。缺点:子等待可以人为延迟(我的用例涉及大量频繁的进程创建/销毁),任何已安装并成功触发的AnyEvent::child
观察者都将自动收割子,而不告诉我的间隔/waitpid计时器,需要编排,和通常感觉就像我在滥用 AnyEvent。