diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1082/Fixture.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1082/Fixture.cs index b401bf42397..6efae946aaa 100644 --- a/src/NHibernate.Test/Async/NHSpecificTest/NH1082/Fixture.cs +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1082/Fixture.cs @@ -9,9 +9,7 @@ using System; -using NHibernate.Cfg; using NUnit.Framework; -using Environment = NHibernate.Cfg.Environment; namespace NHibernate.Test.NHSpecificTest.NH1082 { @@ -40,8 +38,31 @@ public async Task ExceptionsInBeforeTransactionCompletionAbortTransactionAsync() } } - [Test] + public async Task ExceptionsInTransactionSynchronizationBeforeTransactionCompletionAbortTransactionAsync() + { + var c = new C { ID = 1, Value = "value" }; + + var synchronization = new TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion(); + using (ISession s = Sfi.OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + t.RegisterSynchronization(synchronization); + + await (s.SaveAsync(c)); + + Assert.ThrowsAsync(() => t.CommitAsync()); + } + + using (ISession s = Sfi.OpenSession()) + { + var objectInDb = await (s.GetAsync(1)); + Assert.IsNull(objectInDb); + } + } + + // Since v5.2 + [Test, Obsolete] public async Task ExceptionsInSynchronizationBeforeTransactionCompletionAbortTransactionAsync() { var c = new C { ID = 1, Value = "value" }; diff --git a/src/NHibernate.Test/Async/NHSpecificTest/NH1082/TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs b/src/NHibernate.Test/Async/NHSpecificTest/NH1082/TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs new file mode 100644 index 00000000000..960a06b62c4 --- /dev/null +++ b/src/NHibernate.Test/Async/NHSpecificTest/NH1082/TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs @@ -0,0 +1,29 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Transaction; + +namespace NHibernate.Test.NHSpecificTest.NH1082 +{ + using System.Threading.Tasks; + using System.Threading; + public partial class TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion : ITransactionCompletionSynchronization + { + public Task ExecuteBeforeTransactionCompletionAsync(CancellationToken cancellationToken) + { + throw new BadException(); + } + + public Task ExecuteAfterTransactionCompletionAsync(bool success, CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + } +} diff --git a/src/NHibernate.Test/Async/TransactionTest/TransactionNotificationFixture.cs b/src/NHibernate.Test/Async/TransactionTest/TransactionNotificationFixture.cs index 2bde076cffb..0f262c29ddd 100644 --- a/src/NHibernate.Test/Async/TransactionTest/TransactionNotificationFixture.cs +++ b/src/NHibernate.Test/Async/TransactionTest/TransactionNotificationFixture.cs @@ -9,8 +9,9 @@ using System; -using System.Collections; +using System.Data; using System.Data.Common; +using NHibernate.Transaction; using NUnit.Framework; namespace NHibernate.Test.TransactionTest @@ -20,22 +21,23 @@ namespace NHibernate.Test.TransactionTest [TestFixture] public class TransactionNotificationFixtureAsync : TestCase { - protected override string[] Mappings - { - get { return Array.Empty(); } - } + protected override string[] Mappings => Array.Empty(); [Test] public async Task CommitAsync() { var interceptor = new RecordingInterceptor(); using (var session = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) + using (var tx = session.BeginTransaction()) { - ITransaction tx = session.BeginTransaction(); + var synchronisation = new Synchronization(); + tx.RegisterSynchronization(synchronisation); await (tx.CommitAsync()); - Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1)); - Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(1)); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1)); + Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1), "interceptor begin"); + Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(1), "interceptor before"); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1), "interceptor after"); + Assert.That(synchronisation.BeforeExecutions, Is.EqualTo(1), "sync before"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(1), "sync after"); } } @@ -44,26 +46,31 @@ public async Task RollbackAsync() { var interceptor = new RecordingInterceptor(); using (var session = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) + using (var tx = session.BeginTransaction()) { - ITransaction tx = session.BeginTransaction(); + var synchronisation = new Synchronization(); + tx.RegisterSynchronization(synchronisation); await (tx.RollbackAsync()); - Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1)); - Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(0)); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1)); + Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1), "interceptor begin"); + Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(0), "interceptor before"); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1), "interceptor after"); + Assert.That(synchronisation.BeforeExecutions, Is.EqualTo(0), "sync before"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(1), "sync after"); } } - [Theory] [Description("NH2128")] public async Task ShouldNotifyAfterTransactionAsync(bool usePrematureClose) { var interceptor = new RecordingInterceptor(); + var synchronisation = new Synchronization(); ISession s; using (s = OpenSession(interceptor)) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { + t.RegisterSynchronization(synchronisation); await (s.CreateCriteria().ListAsync()); // Call session close while still inside transaction? @@ -72,22 +79,24 @@ public async Task ShouldNotifyAfterTransactionAsync(bool usePrematureClose) } Assert.That(s.IsOpen, Is.False); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1)); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1), "interceptor"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(1), "sync"); } - [Description("NH2128")] [Theory] public async Task ShouldNotifyAfterTransactionWithOwnConnectionAsync(bool usePrematureClose) { var interceptor = new RecordingInterceptor(); + var synchronisation = new Synchronization(); ISession s; using (var ownConnection = await (Sfi.ConnectionProvider.GetConnectionAsync(CancellationToken.None))) { using (s = Sfi.WithOptions().Connection(ownConnection).Interceptor(interceptor).OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { + t.RegisterSynchronization(synchronisation); await (s.CreateCriteria().ListAsync()); // Call session close while still inside transaction? @@ -97,7 +106,56 @@ public async Task ShouldNotifyAfterTransactionWithOwnConnectionAsync(bool usePre } Assert.That(s.IsOpen, Is.False); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1)); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1), "interceptor"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(1), "sync"); + } + } + + #region Synchronization classes + + public partial class CustomTransaction : ITransaction + { + + public Task CommitAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } + + public Task RollbackAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + throw new NotImplementedException(); + } + } + + public partial class Synchronization : ITransactionCompletionSynchronization + { + + public Task ExecuteBeforeTransactionCompletionAsync(CancellationToken cancellationToken) + { + try + { + BeforeExecutions += 1; + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + + public Task ExecuteAfterTransactionCompletionAsync(bool success, CancellationToken cancellationToken) + { + try + { + AfterExecutions += 1; + return Task.CompletedTask; + } + catch (Exception ex) + { + return Task.FromException(ex); + } } } + + #endregion } diff --git a/src/NHibernate.Test/NHSpecificTest/NH1082/Fixture.cs b/src/NHibernate.Test/NHSpecificTest/NH1082/Fixture.cs index 98e6490d859..600816d2896 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1082/Fixture.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1082/Fixture.cs @@ -1,7 +1,5 @@ using System; -using NHibernate.Cfg; using NUnit.Framework; -using Environment = NHibernate.Cfg.Environment; namespace NHibernate.Test.NHSpecificTest.NH1082 { @@ -29,8 +27,31 @@ public void ExceptionsInBeforeTransactionCompletionAbortTransaction() } } - [Test] + public void ExceptionsInTransactionSynchronizationBeforeTransactionCompletionAbortTransaction() + { + var c = new C { ID = 1, Value = "value" }; + + var synchronization = new TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion(); + using (ISession s = Sfi.OpenSession()) + using (ITransaction t = s.BeginTransaction()) + { + t.RegisterSynchronization(synchronization); + + s.Save(c); + + Assert.Throws(t.Commit); + } + + using (ISession s = Sfi.OpenSession()) + { + var objectInDb = s.Get(1); + Assert.IsNull(objectInDb); + } + } + + // Since v5.2 + [Test, Obsolete] public void ExceptionsInSynchronizationBeforeTransactionCompletionAbortTransaction() { var c = new C { ID = 1, Value = "value" }; diff --git a/src/NHibernate.Test/NHSpecificTest/NH1082/SynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs b/src/NHibernate.Test/NHSpecificTest/NH1082/SynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs index 2e091e3115d..c017bac139f 100644 --- a/src/NHibernate.Test/NHSpecificTest/NH1082/SynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs +++ b/src/NHibernate.Test/NHSpecificTest/NH1082/SynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs @@ -1,7 +1,10 @@ +using System; using NHibernate.Transaction; namespace NHibernate.Test.NHSpecificTest.NH1082 { + // Since v5.2 + [Obsolete] public class SynchronizationThatThrowsExceptionAtBeforeTransactionCompletion : ISynchronization { public void BeforeCompletion() @@ -13,4 +16,4 @@ public void AfterCompletion(bool success) { } } -} \ No newline at end of file +} diff --git a/src/NHibernate.Test/NHSpecificTest/NH1082/TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs b/src/NHibernate.Test/NHSpecificTest/NH1082/TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs new file mode 100644 index 00000000000..7e89f5b8e05 --- /dev/null +++ b/src/NHibernate.Test/NHSpecificTest/NH1082/TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion.cs @@ -0,0 +1,16 @@ +using NHibernate.Transaction; + +namespace NHibernate.Test.NHSpecificTest.NH1082 +{ + public partial class TransactionSynchronizationThatThrowsExceptionAtBeforeTransactionCompletion : ITransactionCompletionSynchronization + { + public void ExecuteBeforeTransactionCompletion() + { + throw new BadException(); + } + + public void ExecuteAfterTransactionCompletion(bool success) + { + } + } +} diff --git a/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs b/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs index 0ee12122d02..02d3e5b7e98 100644 --- a/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs +++ b/src/NHibernate.Test/TransactionTest/TransactionNotificationFixture.cs @@ -1,6 +1,7 @@ using System; -using System.Collections; +using System.Data; using System.Data.Common; +using NHibernate.Transaction; using NUnit.Framework; namespace NHibernate.Test.TransactionTest @@ -8,22 +9,22 @@ namespace NHibernate.Test.TransactionTest [TestFixture] public class TransactionNotificationFixture : TestCase { - protected override string[] Mappings - { - get { return Array.Empty(); } - } - + protected override string[] Mappings => Array.Empty(); [Test] public void NoTransaction() { var interceptor = new RecordingInterceptor(); - using (Sfi.WithOptions().Interceptor(interceptor).OpenSession()) + var synchronisation = new Synchronization(); + using (var session = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) { - Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(0)); - Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(0)); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(0)); + session.Transaction.RegisterSynchronization(synchronisation); + Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(0), "interceptor begin"); + Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(0), "interceptor before"); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(0), "interceptor after"); } + Assert.That(synchronisation.BeforeExecutions, Is.EqualTo(0), "sync before"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(0), "sync after"); } [Test] @@ -31,11 +32,16 @@ public void AfterBegin() { var interceptor = new RecordingInterceptor(); using (var session = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) - using (session.BeginTransaction()) + using (var t = session.BeginTransaction()) { - Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1)); - Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(0)); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(0)); + var synchronisation = new Synchronization(); + t.RegisterSynchronization(synchronisation); + + Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1), "interceptor begin"); + Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(0), "interceptor before"); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(0), "interceptor after"); + Assert.That(synchronisation.BeforeExecutions, Is.EqualTo(0), "sync before"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(0), "sync after"); } } @@ -44,12 +50,16 @@ public void Commit() { var interceptor = new RecordingInterceptor(); using (var session = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) + using (var tx = session.BeginTransaction()) { - ITransaction tx = session.BeginTransaction(); + var synchronisation = new Synchronization(); + tx.RegisterSynchronization(synchronisation); tx.Commit(); - Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1)); - Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(1)); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1)); + Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1), "interceptor begin"); + Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(1), "interceptor before"); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1), "interceptor after"); + Assert.That(synchronisation.BeforeExecutions, Is.EqualTo(1), "sync before"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(1), "sync after"); } } @@ -58,26 +68,31 @@ public void Rollback() { var interceptor = new RecordingInterceptor(); using (var session = Sfi.WithOptions().Interceptor(interceptor).OpenSession()) + using (var tx = session.BeginTransaction()) { - ITransaction tx = session.BeginTransaction(); + var synchronisation = new Synchronization(); + tx.RegisterSynchronization(synchronisation); tx.Rollback(); - Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1)); - Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(0)); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1)); + Assert.That(interceptor.afterTransactionBeginCalled, Is.EqualTo(1), "interceptor begin"); + Assert.That(interceptor.beforeTransactionCompletionCalled, Is.EqualTo(0), "interceptor before"); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1), "interceptor after"); + Assert.That(synchronisation.BeforeExecutions, Is.EqualTo(0), "sync before"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(1), "sync after"); } } - [Theory] [Description("NH2128")] public void ShouldNotifyAfterTransaction(bool usePrematureClose) { var interceptor = new RecordingInterceptor(); + var synchronisation = new Synchronization(); ISession s; using (s = OpenSession(interceptor)) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { + t.RegisterSynchronization(synchronisation); s.CreateCriteria().List(); // Call session close while still inside transaction? @@ -86,22 +101,24 @@ public void ShouldNotifyAfterTransaction(bool usePrematureClose) } Assert.That(s.IsOpen, Is.False); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1)); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1), "interceptor"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(1), "sync"); } - [Description("NH2128")] [Theory] public void ShouldNotifyAfterTransactionWithOwnConnection(bool usePrematureClose) { var interceptor = new RecordingInterceptor(); + var synchronisation = new Synchronization(); ISession s; using (var ownConnection = Sfi.ConnectionProvider.GetConnection()) { using (s = Sfi.WithOptions().Connection(ownConnection).Interceptor(interceptor).OpenSession()) - using (s.BeginTransaction()) + using (var t = s.BeginTransaction()) { + t.RegisterSynchronization(synchronisation); s.CreateCriteria().List(); // Call session close while still inside transaction? @@ -111,7 +128,89 @@ public void ShouldNotifyAfterTransactionWithOwnConnection(bool usePrematureClose } Assert.That(s.IsOpen, Is.False); - Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1)); + Assert.That(interceptor.afterTransactionCompletionCalled, Is.EqualTo(1), "interceptor"); + Assert.That(synchronisation.AfterExecutions, Is.EqualTo(1), "sync"); + } + + [Test] + public void CanRegisterSynchronizationInCustomTransaction() + { + using (var custom = new CustomTransaction()) + { + TransactionExtensions.RegisterSynchronization(custom, new Synchronization()); + Assert.That(custom.HasRegisteredSynchronization, Is.True); + } } } + + #region Synchronization classes + + public partial class CustomTransaction : ITransaction + { + public bool HasRegisteredSynchronization { get; private set; } + + public void Dispose() + { + } + + public void Begin() + { + throw new NotImplementedException(); + } + + public void Begin(IsolationLevel isolationLevel) + { + throw new NotImplementedException(); + } + + public void Commit() + { + throw new NotImplementedException(); + } + + public void Rollback() + { + throw new NotImplementedException(); + } + + public bool IsActive => throw new NotImplementedException(); + public bool WasRolledBack => throw new NotImplementedException(); + public bool WasCommitted => throw new NotImplementedException(); + + public void Enlist(DbCommand command) + { + throw new NotImplementedException(); + } + + [Obsolete] + public void RegisterSynchronization(ISynchronization synchronization) + { + throw new NotImplementedException(); + } + + public void RegisterSynchronization(ITransactionCompletionSynchronization synchronization) + { + if (synchronization == null) + throw new ArgumentNullException(nameof(synchronization)); + HasRegisteredSynchronization = true; + } + } + + public partial class Synchronization : ITransactionCompletionSynchronization + { + public int BeforeExecutions { get; private set; } + public int AfterExecutions { get; private set; } + + public void ExecuteBeforeTransactionCompletion() + { + BeforeExecutions += 1; + } + + public void ExecuteAfterTransactionCompletion(bool success) + { + AfterExecutions += 1; + } + } + + #endregion } diff --git a/src/NHibernate/Async/Engine/Transaction/IsolatedWorkAfterTransaction.cs b/src/NHibernate/Async/Engine/Transaction/IsolatedWorkAfterTransaction.cs new file mode 100644 index 00000000000..c9961b56762 --- /dev/null +++ b/src/NHibernate/Async/Engine/Transaction/IsolatedWorkAfterTransaction.cs @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by AsyncGenerator. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + + +using NHibernate.Transaction; + +namespace NHibernate.Engine.Transaction +{ + using System.Threading.Tasks; + using System.Threading; + internal partial class IsolatedWorkAfterTransaction : ITransactionCompletionSynchronization + { + + public Task ExecuteBeforeTransactionCompletionAsync(CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + ExecuteBeforeTransactionCompletion(); + return Task.CompletedTask; + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + + public Task ExecuteAfterTransactionCompletionAsync(bool success, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + if (_session.Factory.Settings.IsDataDefinitionInTransactionSupported) + { + return Isolater.DoIsolatedWorkAsync(_work, _session, cancellationToken); + } + else + { + return Isolater.DoNonTransactedWorkAsync(_work, _session, cancellationToken); + } + } + catch (System.Exception ex) + { + return Task.FromException(ex); + } + } + } +} diff --git a/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs b/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs index 046ff3d4c51..3629a87f063 100644 --- a/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs +++ b/src/NHibernate/Async/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs @@ -70,17 +70,8 @@ protected virtual async Task DropTemporaryTableIfNecessaryAsync(IQueryable persi if (ShouldIsolateTemporaryTableDDL()) { - session.ConnectionManager.Transaction.RegisterSynchronization(new AfterTransactionCompletes((success) => - { - if (Factory.Settings.IsDataDefinitionInTransactionSupported) - { - Isolater.DoIsolatedWork(work, session); - } - else - { - Isolater.DoNonTransactedWork(work, session); - } - })); + session.ConnectionManager.Transaction.RegisterSynchronization( + new IsolatedWorkAfterTransaction(work, session)); } else { diff --git a/src/NHibernate/Async/Transaction/AdoTransaction.cs b/src/NHibernate/Async/Transaction/AdoTransaction.cs index cba158099d8..84097dcb067 100644 --- a/src/NHibernate/Async/Transaction/AdoTransaction.cs +++ b/src/NHibernate/Async/Transaction/AdoTransaction.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; - using NHibernate.Engine; using NHibernate.Impl; @@ -28,7 +27,7 @@ private async Task AfterTransactionCompletionAsync(bool successful, Cancellation cancellationToken.ThrowIfCancellationRequested(); session.ConnectionManager.AfterTransaction(); await (session.AfterTransactionCompletionAsync(successful, this, cancellationToken)).ConfigureAwait(false); - NotifyLocalSynchsAfterTransactionCompletion(successful); + await (NotifyLocalSynchsAfterTransactionCompletionAsync(successful, cancellationToken)).ConfigureAwait(false); foreach (var dependentSession in session.ConnectionManager.DependentSessions) await (dependentSession.AfterTransactionCompletionAsync(successful, this, cancellationToken)).ConfigureAwait(false); @@ -57,7 +56,7 @@ private async Task AfterTransactionCompletionAsync(bool successful, Cancellation log.Debug("Start Commit"); await (session.BeforeTransactionCompletionAsync(this, cancellationToken)).ConfigureAwait(false); - NotifyLocalSynchsBeforeTransactionCompletion(); + await (NotifyLocalSynchsBeforeTransactionCompletionAsync(cancellationToken)).ConfigureAwait(false); foreach (var dependentSession in session.ConnectionManager.DependentSessions) await (dependentSession.BeforeTransactionCompletionAsync(this, cancellationToken)).ConfigureAwait(false); @@ -192,5 +191,74 @@ protected virtual async Task DisposeAsync(bool isDisposing, CancellationToken ca } #endregion + + private async Task NotifyLocalSynchsBeforeTransactionCompletionAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); +#pragma warning disable 612 + if (synchronizations != null) + { + foreach (var sync in synchronizations) +#pragma warning restore 612 + { + try + { + sync.BeforeCompletion(); + } + catch (Exception e) + { + log.Error(e, "exception calling user Synchronization"); + throw; + } + } + } + + if (_completionSynchronizations == null) + return; + + foreach (var sync in _completionSynchronizations) + { + await (sync.ExecuteBeforeTransactionCompletionAsync(cancellationToken)).ConfigureAwait(false); + } + } + + private async Task NotifyLocalSynchsAfterTransactionCompletionAsync(bool success, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + begun = false; + +#pragma warning disable 612 + if (synchronizations != null) + { + foreach (var sync in synchronizations) +#pragma warning restore 612 + { + try + { + sync.AfterCompletion(success); + } + catch (Exception e) + { + log.Error(e, "exception calling user Synchronization"); + } + } + } + + if (_completionSynchronizations == null) + return; + + foreach (var sync in _completionSynchronizations) + { + try + { + await (sync.ExecuteAfterTransactionCompletionAsync(success, cancellationToken)).ConfigureAwait(false); + } + catch (OperationCanceledException) { throw; } + catch (Exception e) + { + log.Error(e, "exception calling user Synchronization"); + } + } + } } } diff --git a/src/NHibernate/Engine/Transaction/IsolatedWorkAfterTransaction.cs b/src/NHibernate/Engine/Transaction/IsolatedWorkAfterTransaction.cs new file mode 100644 index 00000000000..f6b8571c311 --- /dev/null +++ b/src/NHibernate/Engine/Transaction/IsolatedWorkAfterTransaction.cs @@ -0,0 +1,32 @@ +using NHibernate.Transaction; + +namespace NHibernate.Engine.Transaction +{ + internal partial class IsolatedWorkAfterTransaction : ITransactionCompletionSynchronization + { + private readonly IIsolatedWork _work; + private readonly ISessionImplementor _session; + + internal IsolatedWorkAfterTransaction(IIsolatedWork work, ISessionImplementor session) + { + _work = work; + _session = session; + } + + public void ExecuteBeforeTransactionCompletion() + { + } + + public void ExecuteAfterTransactionCompletion(bool success) + { + if (_session.Factory.Settings.IsDataDefinitionInTransactionSupported) + { + Isolater.DoIsolatedWork(_work, _session); + } + else + { + Isolater.DoNonTransactedWork(_work, _session); + } + } + } +} diff --git a/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs b/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs index da6b590f942..2c3c12ade65 100644 --- a/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs +++ b/src/NHibernate/Hql/Ast/ANTLR/Exec/AbstractStatementExecutor.cs @@ -190,17 +190,8 @@ protected virtual void DropTemporaryTableIfNecessary(IQueryable persister, ISess if (ShouldIsolateTemporaryTableDDL()) { - session.ConnectionManager.Transaction.RegisterSynchronization(new AfterTransactionCompletes((success) => - { - if (Factory.Settings.IsDataDefinitionInTransactionSupported) - { - Isolater.DoIsolatedWork(work, session); - } - else - { - Isolater.DoNonTransactedWork(work, session); - } - })); + session.ConnectionManager.Transaction.RegisterSynchronization( + new IsolatedWorkAfterTransaction(work, session)); } else { diff --git a/src/NHibernate/ITransaction.cs b/src/NHibernate/ITransaction.cs index bf12e004475..9ed0a1dff78 100644 --- a/src/NHibernate/ITransaction.cs +++ b/src/NHibernate/ITransaction.cs @@ -70,12 +70,45 @@ public partial interface ITransaction : IDisposable /// It is okay for this to be a no op implementation. /// void Enlist(DbCommand command); - + // Obsolete since 5.2 /// /// Register a user synchronization callback for this transaction. /// /// The callback to register. + [Obsolete("Use RegisterSynchronization(ITransactionCompletionSynchronization) extension method instead. " + + "If implementing ITransaction, implement a 'public void " + + "RegisterSynchronization(ITransactionCompletionSynchronization)': the TransactionExtensions extension " + + "method will call it.")] void RegisterSynchronization(ISynchronization synchronization); } + + // 6.0 TODO: merge into ITransaction + public static class TransactionExtensions + { + /// + /// Register an user synchronization callback for this transaction. + /// + /// The transaction. + /// The callback to register. + public static void RegisterSynchronization( + this ITransaction transaction, + ITransactionCompletionSynchronization synchronization) + { + if (transaction is AdoTransaction adoTransaction) + { + adoTransaction.RegisterSynchronization(synchronization); + return; + } + + // Use reflection for supporting custom transaction factories and transaction implementations. + var registerMethod = transaction.GetType().GetMethod( + nameof(AdoTransaction.RegisterSynchronization), + new[] { typeof(ITransactionCompletionSynchronization) }); + if (registerMethod == null) + throw new NotSupportedException( + $"{transaction.GetType()} does not support {nameof(ITransactionCompletionSynchronization)}"); + registerMethod.Invoke(transaction, new object[] { synchronization }); + } + } } diff --git a/src/NHibernate/Transaction/AdoTransaction.cs b/src/NHibernate/Transaction/AdoTransaction.cs index c413f5757be..f4d222ce66c 100644 --- a/src/NHibernate/Transaction/AdoTransaction.cs +++ b/src/NHibernate/Transaction/AdoTransaction.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; - using NHibernate.Engine; using NHibernate.Impl; @@ -21,7 +20,10 @@ public partial class AdoTransaction : ITransaction private bool committed; private bool rolledBack; private bool commitFailed; + // Since v5.2 + [Obsolete] private IList synchronizations; + private IList _completionSynchronizations; /// /// Initializes a new instance of the class. @@ -83,6 +85,8 @@ public void Enlist(DbCommand command) } } + // Since 5.2 + [Obsolete("Use RegisterSynchronization(ITransactionCompletionSynchronization) instead")] public void RegisterSynchronization(ISynchronization sync) { if (sync == null) throw new ArgumentNullException("sync"); @@ -93,6 +97,19 @@ public void RegisterSynchronization(ISynchronization sync) synchronizations.Add(sync); } + public void RegisterSynchronization(ITransactionCompletionSynchronization synchronization) + { + if (synchronization == null) + throw new ArgumentNullException(nameof(synchronization)); + + // It is tempting to use the session ActionQueue instead, but stateless sessions do not have one. + if (_completionSynchronizations == null) + { + _completionSynchronizations = new List(); + } + _completionSynchronizations.Add(synchronization); + } + public void Begin() { Begin(IsolationLevel.Unspecified); @@ -414,11 +431,12 @@ private void CheckNotZombied() private void NotifyLocalSynchsBeforeTransactionCompletion() { +#pragma warning disable 612 if (synchronizations != null) { - for (int i = 0; i < synchronizations.Count; i++) + foreach (var sync in synchronizations) +#pragma warning restore 612 { - ISynchronization sync = synchronizations[i]; try { sync.BeforeCompletion(); @@ -430,16 +448,26 @@ private void NotifyLocalSynchsBeforeTransactionCompletion() } } } + + if (_completionSynchronizations == null) + return; + + foreach (var sync in _completionSynchronizations) + { + sync.ExecuteBeforeTransactionCompletion(); + } } private void NotifyLocalSynchsAfterTransactionCompletion(bool success) { begun = false; + +#pragma warning disable 612 if (synchronizations != null) { - for (int i = 0; i < synchronizations.Count; i++) + foreach (var sync in synchronizations) +#pragma warning restore 612 { - ISynchronization sync = synchronizations[i]; try { sync.AfterCompletion(success); @@ -450,6 +478,21 @@ private void NotifyLocalSynchsAfterTransactionCompletion(bool success) } } } + + if (_completionSynchronizations == null) + return; + + foreach (var sync in _completionSynchronizations) + { + try + { + sync.ExecuteAfterTransactionCompletion(success); + } + catch (Exception e) + { + log.Error(e, "exception calling user Synchronization"); + } + } } } } diff --git a/src/NHibernate/Transaction/AfterTransactionCompletes.cs b/src/NHibernate/Transaction/AfterTransactionCompletes.cs index 304a75cd5cb..31f0fc32003 100644 --- a/src/NHibernate/Transaction/AfterTransactionCompletes.cs +++ b/src/NHibernate/Transaction/AfterTransactionCompletes.cs @@ -2,6 +2,8 @@ namespace NHibernate.Transaction { + // Since v5.2 + [Obsolete("This class has no more usages and will be removed in a future version")] public class AfterTransactionCompletes : ISynchronization { #region Fields diff --git a/src/NHibernate/Transaction/ISynchronization.cs b/src/NHibernate/Transaction/ISynchronization.cs index f75cf920f5c..fe41e8162f2 100644 --- a/src/NHibernate/Transaction/ISynchronization.cs +++ b/src/NHibernate/Transaction/ISynchronization.cs @@ -5,9 +5,14 @@ namespace NHibernate.Transaction { + // Since v5.2 /// /// A mimic to the javax.transaction.Synchronization callback to enable /// + [Obsolete("Implement ITransactionCompletionSynchronization instead. " + + "If implementing ITransaction, implement a 'public void " + + "RegisterSynchronization(ITransactionCompletionSynchronization)': the TransactionExtensions extension " + + "method will call it.")] public interface ISynchronization { void BeforeCompletion(); diff --git a/src/NHibernate/Transaction/ITransactionCompletionSynchronization.cs b/src/NHibernate/Transaction/ITransactionCompletionSynchronization.cs new file mode 100644 index 00000000000..924e66f1e5e --- /dev/null +++ b/src/NHibernate/Transaction/ITransactionCompletionSynchronization.cs @@ -0,0 +1,11 @@ +using NHibernate.Action; + +namespace NHibernate.Transaction +{ + /// + /// Contract representing processes that needs to occur before or after transaction completion. + /// + public interface ITransactionCompletionSynchronization: IBeforeTransactionCompletionProcess, IAfterTransactionCompletionProcess + { + } +}