use Context::Preserve 'preserve_context';
use Try::Tiny;
use SQL::Abstract qw(is_plain_value is_literal_value);
-use DBIx::Class::_Util qw(quote_sub perlstring serialize);
+use DBIx::Class::_Util qw(quote_sub perlstring serialize detect_reinvoked_destructor);
use namespace::clean;
# default cursor class, overridable in connect_info attributes
}
sub DESTROY {
+ return if &detect_reinvoked_destructor;
+
$_[0]->_verify_pid unless DBIx::Class::_ENV_::BROKEN_FORK;
# some databases spew warnings on implicit disconnect
local $SIG{__WARN__} = sub {};
use B ();
use Carp 'croak';
use Storable 'nfreeze';
-use Scalar::Util qw(weaken blessed reftype);
+use Scalar::Util qw(weaken blessed reftype refaddr);
use List::Util qw(first);
use Sub::Quote qw(qsub quote_sub);
our @EXPORT_OK = qw(
sigwarn_silencer modver_gt_or_eq modver_gt_or_eq_and_lt
fail_on_internal_wantarray fail_on_internal_call
- refdesc refcount hrefaddr is_exception
+ refdesc refcount hrefaddr is_exception detect_reinvoked_destructor
quote_sub qsub perlstring serialize deep_clone
UNRESOLVABLE_CONDITION
);
sub perlstring ($) { q{"}. quotemeta( shift ). q{"} };
-sub hrefaddr ($) { sprintf '0x%x', &Scalar::Util::refaddr||0 }
+sub hrefaddr ($) { sprintf '0x%x', &refaddr||0 }
sub refdesc ($) {
croak "Expecting a reference" if ! length ref $_[0];
sprintf '%s%s(0x%x)',
( defined( $_[1] = blessed $_[0]) ? "$_[1]=" : '' ),
reftype $_[0],
- Scalar::Util::refaddr($_[0]),
+ refaddr($_[0]),
;
}
return $not_blank;
}
+{
+ my $destruction_registry = {};
+
+ sub CLONE {
+ $destruction_registry = { map
+ { defined $_ ? ( refaddr($_) => $_ ) : () }
+ values %$destruction_registry
+ };
+ }
+
+ # This is almost invariably invoked from within DESTROY
+ # throwing exceptions won't work
+ sub detect_reinvoked_destructor {
+
+ # quick "garbage collection" pass - prevents the registry
+ # from slowly growing with a bunch of undef-valued keys
+ defined $destruction_registry->{$_} or delete $destruction_registry->{$_}
+ for keys %$destruction_registry;
+
+ unless (length ref $_[0]) {
+ printf STDERR '%s() expects a reference %s',
+ (caller(0))[3],
+ Carp::longmess,
+ ;
+ return undef; # don't know wtf to do
+ }
+
+ if (! defined $destruction_registry->{ my $addr = refaddr($_[0]) } ) {
+ weaken( $destruction_registry->{$addr} = $_[0] );
+ return 0;
+ }
+ else {
+ carp_unique ( sprintf (
+ 'Preventing *MULTIPLE* DESTROY() invocations on %s - an *EXTREMELY '
+ . 'DANGEROUS* condition which is *ALMOST CERTAINLY GLOBAL* within your '
+ . 'application, affecting *ALL* classes without active protection against '
+ . 'this. Diagnose and fix the root cause ASAP!!!%s',
+ refdesc $_[0],
+ ( ( $INC{'Devel/StackTrace.pm'} and ! do { local $@; eval { Devel::StackTrace->VERSION(2) } } )
+ ? " (likely culprit Devel::StackTrace\@@{[ Devel::StackTrace->VERSION ]} found in %INC, http://is.gd/D_ST_refcap)"
+ : ''
+ )
+ ));
+
+ return 1;
+ }
+ }
+}
+
sub modver_gt_or_eq ($$) {
my ($mod, $ver) = @_;
is(scalar @w, 0, 'no warnings \o/');
}
+# ensure Devel::StackTrace-refcapture-like effects are countered
+{
+ my $s = DBICTest::Schema->connect('dbi:SQLite::memory:');
+ my $g = $s->txn_scope_guard;
+
+ my @arg_capture;
+ {
+ local $SIG{__WARN__} = sub {
+ package DB;
+ my $frnum;
+ while (my @f = caller(++$frnum) ) {
+ push @arg_capture, @DB::args;
+ }
+ };
+
+ undef $g;
+ 1;
+ }
+
+ warnings_exist
+ { @arg_capture = () }
+ qr/\QPreventing *MULTIPLE* DESTROY() invocations on DBIx::Class::Storage::TxnScopeGuard/
+ ;
+}
+
done_testing;