简体   繁体   English

如何在Perl CGI脚本中调试可能的内存泄漏?

[英]How can I debug a possible memory leak in a Perl CGI script?

I have a legacy Perl CGI page running on Apache that processes a large Excel spreadsheet worth of data, adding to the database as necessary. 我在Apache上运行一个旧的Perl CGI页面,该页面可处理大量Excel电子表格中的数据,并根据需要添加到数据库中。 It gets processed in groups of data, and each group of data gets sent to the database. 它以数据组的形式进行处理,每组数据都发送到数据库。

After each call to the database, my system's available memory decreases significantly to the point where there is no memory left. 每次调用数据库后,系统的可用内存都会大大减少,以至于没有剩余内存。 Once I finally get the 'Premature end of script headers' error and HTTP Code 500 is returned to the client, the memory is freed back to the system. 一旦我最终收到“脚本头的结尾过早”错误,并将HTTP Code 500返回给客户端,就将内存释放回系统。

Looking through the (complicated) code, I can't find where the memory leak might be occurring. 通过查看(复杂的)代码,我找不到可能发生内存泄漏的位置。 Is there some trick or tool that I can use to determine where the memory is going? 是否可以使用一些技巧或工具来确定内存的去向?

The short answer is that it sucks to be you. 简短的回答是,做你很烂。 There isn't a nice, ready-to-use program that you can run to get an answer. 没有一个很好的,可以立即使用的程序,您可以运行该程序来获取答案。 I'm sorry that I couldn't be more help, but without seeing any code, etc, there really isn't any better advice that anyone can give. 很抱歉,我无法提供更多帮助,但是如果没有看到任何代码等,确实没有任何人可以提供更好的建议。

I can't talk about your particular situation, but here are some things I've done in the past. 我无法谈谈您的具体情况,但是这是我过去所做的一些事情。 It's important to find out the general area that's causing the problem. 重要的是找出引起问题的一般区域。 It's not much different than other debugging techniques. 与其他调试技术没有太大区别。 Usually I find that there's no elegant solution to these things. 通常,我发现这些问题没有完美的解决方案。 You just roll up your sleeves and stick your arms elbow-deep in the muck no matter how bad it smells. 您只要卷起袖子,就可以将胳膊肘深深地伸入渣土中,无论它有多难闻。

First, run the program outside of the web server. 首先,在Web服务器外部运行程序。 If you still see the problem from the command line, be happy: you just (mostly) ruled out a problem with the web server. 如果仍然从命令行看到问题,请高兴:您只是(大部分)排除了Web服务器的问题。 This might take a little bit of work to make a wrapper script to set up the web environment, but it ends up being much easier since you don't need to mess with restarting the server, etc to reset the environment. 创建包装器脚本来设置Web环境可能需要一点工作,但是由于不需要重新启动服务器等麻烦来重置环境,因此最终变得容易得多。

If you can't replicate the problem outside the server, you can still do what I recommend next, it's just more annoying. 如果您无法在服务器外部复制问题,那么您仍然可以执行我接下来建议的操作,这更令人讨厌。 If it's a webserver problem and not a problem from the command line, the task becomes the discovery about the difference between those two. 如果这是Web服务器问题,而不是命令行问题,则该任务将成为有关这两者之间差异的发现。 I'd encountered situations like that. 我遇到过这样的情况。

If it's not a problem with the web server, start bisecting the script like you would for any debugging problem. 如果Web服务器没有问题,请像对待任何调试问题一样开始将脚本二等分。 If you have logging enabled, turn it on and watch the program run while recording its real memory use. 如果已启用日志记录,请在记录其实际内存使用情况时将其打开并观看程序的运行。 When does it blow up? 什么时候炸毁? It sounds like you have it narrowed down to some database calls. 听起来您已经缩小到一些数据库调用。 If you are able to run this from the command line or the debugger, I'd find a pair appropriate breakpoints before and after the memory increase and gradually bring them closer together. 如果您能够从命令行或调试器运行此命令,那么我会在内存增加之前和之后找到一对合适的断点,并将它们逐渐拉近。 You might use modules such as Devel::Size to look at memory sizes for data structures you suspect. 您可以使用Devel :: Size之类的模块查看您怀疑的数据结构的内存大小。

From there it's just narrowing down the suspects. 从那里开始只是缩小犯罪嫌疑人的范围。 Once you find the suspect, see if you can replicate it in a short example script. 找到嫌疑人后,请查看是否可以在简短的示例脚本中复制它。 You want to eliminate as many contributing-factor possibilities as possible. 您想消除尽可能多的促成因素的可能性。

Once you think you've found the offending code, maybe you can ask another question that shows the code if you still don't understand what's going on. 一旦您认为找到了令人讨厌的代码,也许您仍然可以问另一个问题,如果您仍然不了解正在发生的事情,该问题将显示该代码。

If you wanted to get really fancy, you could write your own Perl debugger. 如果您真的想花哨的话,可以编写自己的Perl调试器。 It's not that hard. 没那么难。 You get a chance to run some subroutines in the DB namespace at the beginning or end of statements. 您有机会在语句的开头或结尾在DB名称空间中运行一些子例程。 You have your debugging code list memory profiles for the stuff you suspect and look for jumps in memory sizes. 您有自己怀疑的内容的调试代码列表内存配置文件,并寻找内存大小的跳跃。 I wouldn't try this unless everything else fails. 除非其他所有方法失败,否则我不会尝试此操作。

If the problem is in the Perl code, you might have a reference that points to itself, or a parent node. 如果问题出在Perl代码中,则可能有一个指向自身或父节点的引用。

Here's a quick sample of code that exhibits this behaviour. 这是展示这种行为的快速代码示例。

{
  my @a;
  @a = [\@a];
}

Usually it comes in the form of an object, that reference a parent object. 通常它以对象的形式出现,该对象引用父对象。

{ package parent;
  sub new{ bless { 'name' => $_[1] }, $_[0] }
  sub add_child{
    my($self,$child_name) = @_;
    my $child = child->new($child_name,$self);
    $self->{$child_name} = $child;   # saves a reference to the child
    return $child;
  }
}
{ package child;
  sub new{
    my($class,$name,$parent) = @_;
    my $self = bless {
      'name' => $name,
      'parent' => $parent # saves a reference to the parent
    }, $class;
    return $self;
  }
}
{
  my $parent = parent->new('Dad');
  my $child  = parent->add_child('Son');

  # At this point both of these are true
  # $parent->{Son}{parent} == $parent
  # $child->{parent}{Son}  == $child

  # Both of the objects **would** be destroyed upon leaving
  # the current scope, except that the object is self-referential
}

# Both objects still exist here, but there is no way to access either of them.

The best way to fix this is to use Scalar::Util::weaken . 解决此问题的最佳方法是使用Scalar :: Util :: weaken

use Scalar::Util qw'weaken';
{ package child;
  sub new{
    my($class,$name,$parent) = @_;
    my $self = bless {
      'name' => $name,
      'parent' => $parent
    }, $class;

    weaken ${$self->{parent}};

    return $self;
  }
}

I would recommend dropping the reference to the parent object, from the child, if at all possible. 如果可能的话,我建议从子对象中删除对父对象的引用。

How are you writing to the database? 您如何写入数据库? If you are using any of the DBI packages, or custom wrappers, make sure that you are flushing any cached objects or cached variables that you can. 如果使用任何DBI软件包或自定义包装,请确保刷新所有缓存的对象或缓存的变量。 These types of memory bloat issues are relatively common, and are usually representative of a shared object cache somewhere that continues to persist. 这些类型的内存膨胀问题相对普遍,通常代表持续存在的共享对象缓存。

Things to try: 尝试的事情:

  • Clear object variables when finished with them 完成对象变量后清除它们
  • Deep-cycle your database connection (this is extreme, but depending on how you are connecting, it may resolve the issue). 深度循环数据库连接(这是极端的操作,但是根据您的连接方式,它可能会解决问题)。
  • Load only one group of data at a time, and flush whatever variables or factory objects load it between groups. 一次仅加载一组数据,然后刷新在组之间加载的任何变量或工厂对象。

Hope that helps some. 希望对您有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM