File Coverage

blib/lib/CatalystX/LeakChecker.pm
Criterion Covered Total %
statement 18 25 72.0
branch 0 2 0.0
condition n/a
subroutine 6 7 85.7
pod 1 1 100.0
total 25 35 71.4


line stmt bran cond sub pod time code
1             package CatalystX::LeakChecker;
2             our $VERSION = '0.06';
3             # ABSTRACT: Debug memory leaks in Catalyst applications
4              
5 1     1   1297381 use Moose::Role;
  1         2  
  1         8  
6 1     1   3512 use B::Deparse;
  1         1  
  1         25  
7 1     1   4 use Text::SimpleTable;
  1         4  
  1         22  
8 1     1   4 use Scalar::Util 'weaken';
  1         1  
  1         93  
9 1     1   456 use Devel::Cycle 'find_cycle';
  1         2382  
  1         3  
10              
11             sub deparse {
12             my ($code) = @_;
13             return q{sub } . B::Deparse->new->coderef2text($code) . q{;};
14             }
15              
16             sub format_table {
17             my @leaks = @_;
18             my $t = Text::SimpleTable->new([52, 'Code'], [ 15, 'Variable' ]);
19             $t->row(@$_) for map { [deparse($_->{code}), $_->{var}] } @leaks;
20             return $t->draw;
21             }
22              
23             sub format_leak {
24             my ($leak, $sym) = @_;
25             my @lines;
26             my $ret = '$ctx';
27             for my $element (@{ $leak }) {
28             my ($type, $index, $ref, $val, $weak) = @{ $element };
29             die $type if $weak;
30             if ($type eq 'HASH') {
31             $ret .= qq(->{$index}) if $type eq 'HASH';
32             }
33             elsif ($type eq 'ARRAY') {
34             $ret .= qq(->[$index]) if $type eq 'ARRAY';
35             }
36             elsif ($type eq 'SCALAR') {
37             $ret = qq(\${ ${ret} });
38             }
39             elsif ($type eq 'CODE') {
40             push @lines, qq(\$${$sym} = ${ret};);
41             push @lines, qq{code reference \$${$sym} deparses to: } . deparse($ref);
42             $ret = qq($index);
43             ${ $sym }++;
44             }
45             }
46             return join qq{\n} => @lines, $ret;
47             }
48              
49 1     1   395 use namespace::clean -except => 'meta';
  1         1  
  1         8  
50              
51              
52             sub found_leaks {
53 0     0 1   my ($ctx, @leaks) = @_;
54 0           my $t = Text::SimpleTable->new(70);
55              
56 0           my $sym = 'a';
57 0           for my $leak (@leaks) {
58 0           $t->row(format_leak($leak, \$sym), '');
59             }
60              
61 0           my $msg = "Circular reference detected:\n" . $t->draw;
62 0 0         $ctx->log->debug($msg) if $ctx->debug;
63             }
64              
65             after finalize => sub {
66             my ($ctx) = @_;
67             my @leaks;
68              
69             my $weak_ctx = $ctx;
70             weaken $weak_ctx;
71              
72             find_cycle($ctx, sub {
73             my ($path) = @_;
74             push @leaks, $path
75             if $path->[0]->[2] == $weak_ctx;
76             });
77             return unless @leaks;
78              
79             $ctx->found_leaks(@leaks);
80             };
81              
82             1;
83              
84             __END__
85             =pod
86              
87             =head1 NAME
88              
89             CatalystX::LeakChecker - Debug memory leaks in Catalyst applications
90              
91             =head1 VERSION
92              
93             version 0.06
94              
95             =head1 SYNOPSIS
96              
97             package MyApp;
98             use namespace::autoclean;
99              
100             extends 'Catalyst';
101             with 'CatalystX::LeakChecker';
102              
103             __PACKAGE__->setup;
104              
105             =head1 DESCRIPTION
106              
107             It's easy to create memory leaks in Catalyst applications and often they're
108             hard to find. This module tries to help you finding them by automatically
109             checking for common causes of leaks.
110              
111             This module is intended for debugging only. I suggest to not enable it in a
112             production environment.
113              
114             =head1 METHODS
115              
116             =head2 found_leaks(@leaks)
117              
118             If any leaks were found, this method is called at the end of each request. A
119             list of leaks is passed to it. It logs a debug message like this:
120              
121             [debug] Circular reference detected:
122             +------------------------------------------------------+-----------------+
123             | $ctx->{stash}->{ctx} |
124             '------------------------------------------------------+-----------------'
125              
126             It's also able to find leaks in code references. A debug message for that might
127             look like this:
128              
129             [debug] Circular reference detected:
130             +------------------------------------------------------+-----------------+
131             | $a = $ctx->{stash}->{leak_closure}; |
132             | code reference $a deparses to: sub { |
133             | package TestApp::Controller::Affe; |
134             | use warnings; |
135             | use strict 'refs'; |
136             | $ctx->response->body('from leaky closure'); |
137             | }; |
138             | ${ $ctx } |
139             '------------------------------------------------------+-----------------'
140              
141             Override this method if you want leaks to be reported differently.
142              
143             =head1 AUTHORS
144              
145             Florian Ragwitz <rafl@debian.org>
146             Tomas Doran <bobtfish@bobtfish.net>
147              
148             =head1 COPYRIGHT AND LICENSE
149              
150             This software is copyright (c) 2010 by Florian Ragwitz.
151              
152             This is free software; you can redistribute it and/or modify it under
153             the same terms as the Perl 5 programming language system itself.
154              
155             =cut
156