From 29bdb5e47b1b47066aa77fa22574ab4eb5b3ca7a Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 17 Apr 2015 11:25:39 -0700 Subject: [PATCH 1/3] Storage::TxnScopeGuard: Document how to rollback a scoped transaction --- lib/DBIx/Class/Storage/TxnScopeGuard.pm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/DBIx/Class/Storage/TxnScopeGuard.pm b/lib/DBIx/Class/Storage/TxnScopeGuard.pm index edf72057f..0c8c77d74 100644 --- a/lib/DBIx/Class/Storage/TxnScopeGuard.pm +++ b/lib/DBIx/Class/Storage/TxnScopeGuard.pm @@ -136,6 +136,15 @@ DBIx::Class::Storage::TxnScopeGuard - Scope-based transaction handling An object that behaves much like L, but hardcoded to do the right thing with transactions in DBIx::Class. +If you get the urge to call a C method on the guard object, you're +advised to instead wrap your scoped transaction using C<< L >> +or L and throw an exception with C<< L >>. +Explicit rollbacks don't compose (or nest) nicely without unwinding the scope +via an exception. + +A warning is emitted if the guard goes out of scope without being first +inactivated by L or an exception. + =head1 METHODS =head2 new From a0adfa73b3e2dd98ea9fd797f0426225ee451d36 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 17 Apr 2015 11:26:08 -0700 Subject: [PATCH 2/3] Storage::TxnScopeGuard: Add a force_inactivate method for completeness Recommend not using it, although there are suboptimal use cases. --- lib/DBIx/Class/Storage/TxnScopeGuard.pm | 11 +++++++++++ t/storage/txn_scope_guard.t | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/DBIx/Class/Storage/TxnScopeGuard.pm b/lib/DBIx/Class/Storage/TxnScopeGuard.pm index 0c8c77d74..1575a8c4d 100644 --- a/lib/DBIx/Class/Storage/TxnScopeGuard.pm +++ b/lib/DBIx/Class/Storage/TxnScopeGuard.pm @@ -49,6 +49,11 @@ sub commit { $self->{inactivated} = 1; } +sub force_inactivate { + my $self = shift; + $self->{inactivated} = 1; +} + sub DESTROY { return if &detected_reinvoked_destructor; @@ -159,6 +164,12 @@ Commit the transaction, and stop guarding the scope. If this method is not called and this object goes out of scope (e.g. an exception is thrown) then the transaction is rolled back, via L +=head2 force_inactivate + +Forcibly inactivate the guard, causing it to stop guarding the scope without +committing. You're advised not to use this and to throw an exception if you +want to abort the transaction. See the L. + =cut =head1 SEE ALSO diff --git a/t/storage/txn_scope_guard.t b/t/storage/txn_scope_guard.t index afe8c8e46..9d449499f 100644 --- a/t/storage/txn_scope_guard.t +++ b/t/storage/txn_scope_guard.t @@ -242,4 +242,19 @@ for my $post_poison (0,1) { ; } +# force_inactivate actually inactivates and doesn't emit a warning +{ + my $s = DBICTest::Schema->connect('dbi:SQLite::memory:'); + + my @warnings; + local $SIG{__WARN__} = sub { + push @warnings, @_; + }; + { + my $g = $s->txn_scope_guard; + $g->force_inactivate; + } + is scalar @warnings, 0, "no warning"; +} + done_testing; From f7cbe4cdbbb2c4c26d7dd91060b61745f1b758c9 Mon Sep 17 00:00:00 2001 From: Thomas Sibley Date: Fri, 17 Apr 2015 11:28:41 -0700 Subject: [PATCH 3/3] Storage::TxnScopeGuard: Failing test for warning during global destruction When global destruction is triggered by an exception. --- .../txn_scope_guard_global_destruction.t | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 t/storage/txn_scope_guard_global_destruction.t diff --git a/t/storage/txn_scope_guard_global_destruction.t b/t/storage/txn_scope_guard_global_destruction.t new file mode 100644 index 000000000..3a539ac8b --- /dev/null +++ b/t/storage/txn_scope_guard_global_destruction.t @@ -0,0 +1,49 @@ +use strict; +use warnings; +use Test::More; + +sub run_snippet { + my $output = `$^X -Mlib=t/lib -MDBICTest -e '$_[0]' 2>&1`; + chomp $output; + return $output; +} + +{ + my $output = run_snippet(' + my $s = DBICTest->init_schema; + eval { + my $g = $s->txn_scope_guard; + die "normal destruction"; + }; + '); + unlike $output, + qr/A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error\. Rolling back\./, + 'Out of scope warning not detected'; + is $output, "", "No other output"; +} + +{ + my $output = run_snippet(' + my $s = DBICTest->init_schema; + my $g = $s->txn_scope_guard; + die "global destruction"; + '); + unlike $output, + qr/A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error\. Rolling back\./, + 'Out of scope warning not detected'; + like $output, + qr/global destruction/, + 'Fatal exception detected'; +} + +{ + my $output = run_snippet(' + my $s = DBICTest->init_schema; + my $g = $s->txn_scope_guard; + '); + like $output, + qr/A DBIx::Class::Storage::TxnScopeGuard went out of scope without explicit commit or error\. Rolling back\./, + 'Out of scope warning detected'; +} + +done_testing;