我偶尔会让Apache进程占用100%的CPU使用率而且永远不会退出。当这发生8次(8个CPU)时,服务器变得无法使用。根据服务器状态,“挂起”进程是一个相当复杂的自定义Perl程序,但是当我向Perl中的错误日志打印警告时,它表明进程总是完成并返回,但显然在返回之后,它进入一个循环或什么的。当我在进程上运行strace时,它只显示了大量的mmap2 / munmap行,例如:
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4329472, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d7000
mremap(0xb3d7c000, 4329472, 4333568, MREMAP_MAYMOVE) = 0xb3d7c000
munmap(0xb42d7000, 4329472) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
munmap(0xb42d6000, 4333568) = 0
mmap2(NULL, 4333568, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb42d6000
我不知道这意味着什么。它无限期地这样做。有没有办法更高层次地了解它正在做什么?有没有人见过类似的东西?
此外,虽然它通常是一个相当随机的偶然事件,如果我在退出Perl程序之前发出rflush(),这几乎总会发生。
我正在使用mod_perl / 2.0.7,perl / 5.12.4,apache / 2.2.24。这也发生在一些次要版本中;我升级了它并没有改善任何东西。我也在使用DBI,DBD:ODBC。
我最好的猜测是某种争用/竞争条件,但用“警告”输出跟踪代码表明Perl本身没有这样的问题。 Perl代码还使用具有永远不会被触发的超时警报信号的evals,因此它似乎不可能成为Perl代码的问题。
非常感谢任何想法。
答案 0 :(得分:0)
我想我已经解决了。仍然不知道为什么rflush()会使情况变得更糟,我也没有找到任何更高级别的strace结果视图,但这就是它为什么这样做的原因:
在某些时候,我将以下代码添加到“错误”子程序中,只要程序检测到内部错误,就会调用该程序:
my $caller = "";
my $x = 1;
while (caller($x) && $x < 10)
{
my $subroutine = (caller($x))[3];
$subroutine =~ s/^.*::([^:]+?)$/$1/gis;
my $line = (caller($x))[2];
$caller = qq~->$subroutine(line $line)~.$caller if $subroutine;
}
$caller = "main".$caller;
所有这一切都打印出错误发生在哪个子程序中。因此,例如,如果在程序的主体中,子程序“sub1”从第1234行调用,并从那里“sub2”从行调用2345,并且在线3456上的“sub2”中发生错误,那么“错误”子程序将注意到错误发生在“主(行1234) - &gt; sub1(行2345) - &gt; sub2(行3456)” 。显然,这对于调试很重要。不幸的是,它无法帮助调试“错误”子程序本身!
在“while”循环中,它会检查以确保$ x&lt; 10(不应该有10级子程序调用)。这是为了防止失控迭代。不幸的是,缺少实际递增变量$ x的行。这意味着它将继续检查$ x&lt; 10无限期,因为$ x总是= 1.关于这一点的棘手部分是从信号陷阱处理程序调用“错误”子程序,它显然是从主程序本身分配的,所以“错误”子程序可以在这个无限循环中运行而程序的主体似乎完成了。这使得无法“打印”或“警告”对正在发生的事情的线性视图,导致极大的混乱。这也使得这个无限循环超出了我的主“eval”,它有一个超时,因此超时从未像预期的那样停止循环。程序完成到最后一行和输出,但是服务它的过程一直在继续,由于信号处理程序中的这个无限循环而占用了100%的CPU。
解决方案只有四个字符:“$ x ++”。在while循环中添加它可以防止无限次迭代,并且实际上允许来自信号处理程序的调试信息打印出来。