specific DateTime::Format dependencies
* Fixes
+ - Protect destructors from rare but possible double execution, and
+ loudly warn the user whenever the problem is encountered (GH#63)
- Fix updating multiple CLOB/BLOB columns on Oracle
- Fix incorrect collapsing-parser source being generated in the
presence of unicode data among the collapse-points
__PACKAGE__->mk_group_accessors(inherited => '_skip_namespace_frames');
__PACKAGE__->_skip_namespace_frames('^DBIx::Class|^SQL::Abstract|^Try::Tiny|^Class::Accessor::Grouped|^Context::Preserve');
+# FIXME - this is not really necessary, and is in
+# fact going to slow things down a bit
+# However it is the right thing to do in order to get
+# various install bases to highlight their brokenness
+# Remove at some unknown point in the future
+sub DESTROY { &DBIx::Class::_Util::detected_reinvoked_destructor }
+
sub mk_classdata {
shift->mk_classaccessor(@_);
}
use strict;
use warnings;
+use DBIx::Class::_Util 'detected_reinvoked_destructor';
+use namespace::clean;
sub DESTROY {
+ return if &detected_reinvoked_destructor;
+
my ($self) = @_;
my $class = ref $self;
warn "$class $self destroyed without saving changes to "
my $global_phase_destroy;
sub DESTROY {
+ ### NO detected_reinvoked_destructor check
+ ### This code very much relies on being called multuple times
+
return if $global_phase_destroy ||= in_global_destruction;
######
my $global_phase_destroy;
sub DESTROY {
+ ### NO detected_reinvoked_destructor check
+ ### This code very much relies on being called multuple times
+
return if $global_phase_destroy ||= in_global_destruction;
my $self = shift;
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 detected_reinvoked_destructor);
use namespace::clean;
# default cursor class, overridable in connect_info attributes
}
sub DESTROY {
+ return if &detected_reinvoked_destructor;
+
$_[0]->_verify_pid unless DBIx::Class::_ENV_::BROKEN_FORK;
# some databases spew warnings on implicit disconnect
local $SIG{__WARN__} = sub {};
use Try::Tiny;
use Scalar::Util qw(refaddr weaken);
use List::Util 'shuffle';
+use DBIx::Class::_Util 'detected_reinvoked_destructor';
use namespace::clean;
__PACKAGE__->mk_group_accessors('simple' =>
sub DESTROY {
+ return if &detected_reinvoked_destructor;
+
$_[0]->__finish_sth if $_[0]->{sth};
}
use strict;
use warnings;
use Try::Tiny;
-use Scalar::Util qw/weaken blessed refaddr/;
+use Scalar::Util qw(weaken blessed refaddr);
use DBIx::Class;
-use DBIx::Class::_Util 'is_exception';
+use DBIx::Class::_Util qw(is_exception detected_reinvoked_destructor);
use DBIx::Class::Carp;
use namespace::clean;
}
sub DESTROY {
+ return if &detected_reinvoked_destructor;
+
my $self = shift;
return if $self->{inactivated};
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 detected_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 detected_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;
+
+ if (! length ref $_[0]) {
+ printf STDERR '%s() expects a blessed reference %s',
+ (caller(0))[3],
+ Carp::longmess,
+ ;
+ return undef; # don't know wtf to do
+ }
+ elsif (! 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) = @_;
use DBICTest::Util 'local_umask';
use DBICTest::Schema;
use DBICTest::Util::LeakTracer qw/populate_weakregistry assert_empty_weakregistry/;
+use DBIx::Class::_Util 'detected_reinvoked_destructor';
use Carp;
use Path::Class::File ();
use File::Spec;
$dbh->{Callbacks} = {
connect => sub { $guard_cb->('connect') },
disconnect => sub { $guard_cb->('disconnect') },
- DESTROY => sub { $guard_cb->('DESTROY') },
+ DESTROY => sub { &detected_reinvoked_destructor; $guard_cb->('DESTROY') },
};
}
},
use Config;
use Carp 'confess';
use Scalar::Util qw(blessed refaddr);
+use DBIx::Class::_Util;
use base 'Exporter';
our @EXPORT_OK = qw(local_umask stacktrace check_customcond_args visit_namespaces);
{
package DBICTest::Util::UmaskGuard;
sub DESTROY {
+ &DBIx::Class::_Util::detected_reinvoked_destructor;
+
local ($@, $!);
eval { defined (umask ${$_[0]}) or die };
warn ( "Unable to reset old umask ${$_[0]}: " . ($!||'Unknown error') )
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;