Skip Menu |

This queue is for tickets about the DBI CPAN distribution.

Report information
The Basics
Id: 113852
Status: resolved
Priority: 0/
Queue: DBI

People
Owner: Nobody in particular
Requestors: NERDVANA [...] cpan.org
Cc: ribasushi [...] leporine.io
AdminCc:

Bug Information
Severity: Normal
Broken in:
  • 1.633
  • 1.634
Fixed in: (no value)



Subject: Use of Tie::Cache::LRU (as documented) leaks memory
I've done some extensive testing on this problem, and determined that when Tie::Cache::LRU is used for the ->{CachedKids} field of a database handle, the database handle, all cached statement handles, and the CachedKids itself do not get freed when the database handle goes out of scope. Tie::Cache::LRU does not cause this problem on its own, even when used in a similar structure to DBI. DBI does not exhibit the problem unless it is used with Tie::Cache::LRU. Some interaction between DBI and Tie::Cache::LRU causes it to fail to garbage-collect. I'm reporting this as a bug in DBI because the documentation officially recommends Tie::Cache::LRU to solve the problem of limiting the number prepared statements. The following are some easy ways to re-produce my findings: # This is a minimal example that does leak, and is prescribed by the # DBI documentation. It leaks on all of: # Linux, perl 5.20.0, DBI 1.633, MySQL backend # Linux, perl 5.12.3, DBI 1.634, MySQL backend # Linux, perl 5.8.9, DBI 1.634, MySQL backend # -> and warns about freeing unreferenced scalars during global destruction! # Win32, perl 5.20.2, DBI 1.633, ODBC SQLServer backend perl -MScalar::Util -MTie::Cache::LRU -MDevel::Gladiator -MDBI -e ' use strict; use warnings; my ($dsn, $u, $p, $table)= @ARGV; for (1..100) { my $dbh= DBI->connect($dsn, $u, $p, {}) // last; { my $cache; my $t= tie %$cache, "Tie::Cache::LRU", 10; $dbh->{CachedKids}= $cache; } $dbh->prepare_cached("select count(*) from $table"); } print Devel::Gladiator::arena_table()' DSN USER PASS TABLE | grep Tie # This parody of DBI data structures does not leak perl objects # when created/destroyed in a loop. perl -MScalar::Util -MTie::Cache::LRU -MDevel::Gladiator -e ' for (1..100) { my %container; tie %container, "Tie::Cache::LRU", 100; my $dbh= \%container; my %cache; tie %cache, "Tie::Cache::LRU", 100; $dbh->{CachedKids}= \%cache; $dbh->{CachedKids}{1}= bless {}, "main"; $dbh->{CachedKids}{2} ||= bless {}, "main"; Scalar::Util::weaken( $dbh->{CachedKids}{2}{Database}= $dbh ); }' These are excerpts from the DBI.pm module: ---------------------------------------------------------------------------- Because of the way the Perl tie mechanism works you cannot reliably use the C<||=> operator directly to initialise the attribute, like this: my $foo = $dbh->{private_yourmodname_foo} ||= { ... }; # WRONG you should use a two step approach like this: my $foo = $dbh->{private_yourmodname_foo}; $foo ||= $dbh->{private_yourmodname_foo} = { ... }; ---------------------------------------------------------------------------- my $cache = $dbh->{CachedKids} ||= {}; ----------------------------------------------------------------------------
Subject: Use of *any* tied cache leaks the cache and the inner dbh handle
On Mon Apr 18 03:50:12 2016, NERDVANA wrote: Show quoted text
> I've done some extensive testing on this problem, and determined that > when Tie::Cache::LRU is used for the ->{CachedKids} field of a > database handle, the database handle, all cached statement handles, > and the CachedKids itself do not get freed when the database handle > goes out of scope. >
This is not related to Tie::Cache::LRU. The real problem is that *any* tied cache is not freed upon $dbh destruction. Attached is a minimal test demonstrating the problem (the dependency on DBD::SQlite likely isn't needed either). A sample run looks like: 1..9 ok 1 - DBI::db=HASH(0x25812a0) cache tied ok 2 - One cached statement found in statement cache of DBI::db=HASH(0x25812a0) (HASH(0x23d2ce0)) ok 3 - One cached statement found in statement cache of DBI::db=HASH(0x2584528) (HASH(0x25af9d8)) ok 4 - Refcount of statement cache of DBI::db=HASH(0x25812a0) (HASH(0x23d2ce0)) correct ok 5 - Refcount of statement cache of DBI::db=HASH(0x2584528) (HASH(0x25af9d8)) correct ok 6 - DBI::db=HASH(0x2584528) garbage collected ok 7 - DBI::db=HASH(0x25812a0) garbage collected not ok 8 - statement cache of DBI::db=HASH(0x25812a0) (HASH(0x23d2ce0)) garbage collected # Failed test 'statement cache of DBI::db=HASH(0x25812a0) (HASH(0x23d2ce0)) garbage collected' # at dbi_argh.txt line 61. # got: 'HASH(0x23d2ce0)' # expected: undef ok 9 - statement cache of DBI::db=HASH(0x2584528) (HASH(0x25af9d8)) garbage collected # Looks like you failed 1 test of 9.
Subject: dbi_argh.txt
use warnings; use strict; use Scalar::Util qw( weaken reftype refaddr blessed ); use DBI; use B (); use Tie::Hash (); use Test::More tests => 9; my (%weak_dbhs, %weak_caches); # past this scope everything should be gone { ### get two identical connections my @dbhs = map { DBI->connect('dbi:SQLite::memory:') } (1,2); ### get weakrefs on both handles %weak_dbhs = map { refdesc($_) => $_ } @dbhs; weaken $_ for values %weak_dbhs; ### get the first one's cache tied { ok( tie( my %cache, 'Tie::StdHash'), refdesc($dbhs[0]) . ' cache tied' ); $dbhs[0]->{CachedKids} = \%cache; } ### prepare something on both $_->prepare_cached( 'SELECT 1' ) for @dbhs; ### get weakrefs of both caches %weak_caches = map { sprintf( 'statement cache of %s (%s)', refdesc($_), refdesc($_->{CachedKids}) ) => $_->{CachedKids} } @dbhs; weaken $_ for values %weak_caches; ### check both caches have entries is (scalar keys %{$weak_caches{$_}}, 1, "One cached statement found in $_") for keys %weak_caches; ### check both caches have sane refcounts is ( refcount( $weak_caches{$_} ), 1, "Refcount of $_ correct" ) for keys %weak_caches; } # check both $dbh weakrefs are gone is ($weak_dbhs{$_}, undef, "$_ garbage collected") for keys %weak_dbhs; is ($weak_caches{$_}, undef, "$_ garbage collected") for keys %weak_caches; sub refdesc { sprintf '%s%s(0x%x)', ( defined( $_[1] = blessed $_[0]) ? "$_[1]=" : '' ), reftype $_[0], refaddr($_[0]), ; } sub refcount { B::svref_2object($_[0])->REFCNT; }
Thanks for the report and the test cases. I plan to work on this ticket this weekend (at the QAH).
If I comment out the sprintf that calls $_->{CachedKids} the bug doesn't show up. (Thanks again for the test cases!) Turns out this issue relates to the caching of handle attributes. Pondering...
Fixed by 7a8ca53.
Scratch that (the commit was incomplete). Fixed by ad5b793.
Fixed in DBI 1.635