use Carp;
use Scalar::Util qw(isweak weaken blessed reftype);
-use DBIx::Class::_Util 'refcount';
+use DBIx::Class::_Util qw(refcount hrefaddr);
use DBIx::Class::Optional::Dependencies;
use Data::Dumper::Concise;
use DBICTest::Util 'stacktrace';
use constant {
CV_TRACING => DBIx::Class::Optional::Dependencies->req_ok_for ('test_leaks_heavy'),
+ SKIP_SCALAR_REFS => ( $] > 5.017 ) ? 1 : 0,
};
use base 'Exporter';
-our @EXPORT_OK = qw(populate_weakregistry assert_empty_weakregistry hrefaddr visit_refs);
+our @EXPORT_OK = qw(populate_weakregistry assert_empty_weakregistry visit_refs);
my $refs_traced = 0;
my $leaks_found = 0;
my %reg_of_regs;
-sub hrefaddr { sprintf '0x%x', &Scalar::Util::refaddr }
-
# so we don't trigger stringification
sub _describe_ref {
sprintf '%s%s(%s)',
for keys %$reg;
}
+ # FIXME/INVESTIGATE - something fishy is going on with refs to plain
+ # strings, perhaps something to do with the CoW work etc...
+ return $target if SKIP_SCALAR_REFS and reftype($target) eq 'SCALAR';
+
if (! defined $weak_registry->{$refaddr}{weakref}) {
$weak_registry->{$refaddr} = {
stacktrace => stacktrace(1),
next unless length ref $r;
+ # no diving into weakregistries
+ next if $reg_of_regs{hrefaddr $r};
+
next if $args->{seen_refs}{my $dec_addr = Scalar::Util::refaddr($r)}++;
$visited_cnt++;
$args->{action}->($r) or next;
- my $type = reftype $r;
- if ($type eq 'HASH') {
- $visited_cnt += visit_refs({ %$args, refs => [ map {
- ( !isweak($r->{$_}) ) ? $r->{$_} : ()
- } keys %$r ] });
- }
- elsif ($type eq 'ARRAY') {
- $visited_cnt += visit_refs({ %$args, refs => [ map {
- ( !isweak($r->[$_]) ) ? $r->[$_] : ()
- } 0..$#$r ] });
- }
- elsif ($type eq 'REF' and !isweak($$r)) {
- $visited_cnt += visit_refs({ %$args, refs => [ $$r ] });
- }
- elsif (CV_TRACING and $type eq 'CODE') {
- $visited_cnt += visit_refs({ %$args, refs => [ map {
- ( !isweak($_) ) ? $_ : ()
- } scalar PadWalker::closed_over($r) ] }); # scalar due to RT#92269
- }
+ # This may end up being necessarry some day, but do not slow things
+ # down for now
+ #if ( defined( my $t = tied($r) ) ) {
+ # $visited_cnt += visit_refs({ %$args, refs => [ $t ] });
+ #}
+
+ local $@;
+ eval {
+ my $type = reftype $r;
+ if ($type eq 'HASH') {
+ $visited_cnt += visit_refs({ %$args, refs => [ map {
+ ( !isweak($r->{$_}) ) ? $r->{$_} : ()
+ } keys %$r ] });
+ }
+ elsif ($type eq 'ARRAY') {
+ $visited_cnt += visit_refs({ %$args, refs => [ map {
+ ( !isweak($r->[$_]) ) ? $r->[$_] : ()
+ } 0..$#$r ] });
+ }
+ elsif ($type eq 'REF' and !isweak($$r)) {
+ $visited_cnt += visit_refs({ %$args, refs => [ $$r ] });
+ }
+ elsif (CV_TRACING and $type eq 'CODE') {
+ $visited_cnt += visit_refs({ %$args, refs => [ map {
+ ( !isweak($_) ) ? $_ : ()
+ } scalar PadWalker::closed_over($r) ] }); # scalar due to RT#92269
+ }
+ 1;
+ } or warn "Could not descend into @{[ _describe_ref($r) ]}: $@\n";
}
$visited_cnt;
}
croak 'Expecting a registry hashref' unless ref $weak_registry eq 'HASH';
+ defined $weak_registry->{$_}{weakref} or delete $weak_registry->{$_}
+ for keys %$weak_registry;
+
return unless keys %$weak_registry;
my $tb = eval { Test::Builder->new }
next if ! defined $weak_registry->{$addr}{weakref};
- $leaks_found++;
+ $leaks_found++ unless $tb->in_todo;
$tb->ok (0, "Leaked $weak_registry->{$addr}{display_name}");
my $diag = do {
;
};
+ # FIXME - need to add a circular reference seeker based on the visitor
+ # (will need a bunch of modifications, punting with just a stub for now)
+
$diag .= Devel::FindRef::track ($weak_registry->{$addr}{weakref}, 50) . "\n"
if ( $ENV{TEST_VERBOSE} && eval { require Devel::FindRef });
}
$tb->diag($diag);
+
+# if ($leaks_found == 1) {
+# # using the fh dumper due to intermittent buffering issues
+# # in case we decide to exit soon after (possibly via _exit)
+# require Devel::MAT::Dumper;
+# local $Devel::MAT::Dumper::MAX_STRING = -1;
+# open( my $fh, '>:raw', "leaked_${addr}_pid$$.pmat" ) or die $!;
+# Devel::MAT::Dumper::dumpfh( $fh );
+# close ($fh) or die $!;
+#
+# use POSIX;
+# POSIX::_exit(1);
+# }
}
- if (! $quiet and ! $leaks_found) {
+ if (! $quiet and !$leaks_found and ! $tb->in_todo) {
$tb->ok(1, sprintf "No leaks found at %s line %d", (caller())[1,2] );
}
}