Skip to content

Commit 9d630f9

Browse files
committed
NH-3807 - Distributed transactions not supported in .netstandard.
Enlisted transactions are still supported, as long as they are single-phase with the durable resource.
1 parent b8829b4 commit 9d630f9

File tree

4 files changed

+105
-6
lines changed

4 files changed

+105
-6
lines changed

src/NHibernate.Test/NHSpecificTest/NH2420/Fixture.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,11 @@ public void ShouldBeAbleToReleaseSuppliedConnectionAfterDistributedTransaction()
6565
EnlistmentOptions.None);
6666

6767
DbConnection connection;
68-
#if !NETCOREAPP2_0
6968
if (sessions.ConnectionProvider.Driver.GetType() == typeof(OdbcDriver))
7069
{
7170
connection = new OdbcConnection(connectionString);
7271
}
7372
else
74-
#endif
7573
{
7674
connection = new SqlConnection(connectionString);
7775
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
1919
<Compile Remove="DriverTest\**" />
20+
<Compile Remove="NHSpecificTest\DtcFailures\**" /> <!--Distributed-->
2021
<Compile Remove="NHSpecificTest\NH2188\**" />
22+
<Compile Remove="NHSpecificTest\NH2420\**" /> <!--Distributed-->
2123
<Compile Remove="NHSpecificTest\NH2484\**" />
2224
<Compile Remove="NHSpecificTest\NH2985\**" />
2325
<Compile Remove="NHSpecificTest\NH3121\**" />

src/NHibernate.Test/TestCase.cs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,13 @@ public void TearDown()
167167
var wereClosed = CheckSessionsWereClosed();
168168
var wasCleaned = CheckDatabaseWasCleaned();
169169
var wereConnectionsClosed = CheckConnectionsWereClosed();
170-
fail = !wereClosed || !wasCleaned || !wereConnectionsClosed;
170+
var wereTransactionsDisposed = CheckEnlistedTransactionsWereDisposed();
171+
fail = !wereClosed || !wasCleaned || !wereConnectionsClosed || !wereTransactionsDisposed;
171172

172173
if (fail)
173174
{
174175
badCleanupMessage = "Test didn't clean up after itself. session closed: " + wereClosed + "; database cleaned: " + wasCleaned
175-
+ "; connection closed: " + wereConnectionsClosed;
176+
+ "; connection closed: " + wereConnectionsClosed + "; transactions disposed:" + wereTransactionsDisposed;
176177
if (testResult != null && testResult.Outcome.Status == TestStatus.Failed)
177178
{
178179
// Avoid hiding a test failure (asserts are usually not hidden, but other exception would be).
@@ -270,6 +271,31 @@ private bool CheckConnectionsWereClosed()
270271
return false;
271272
}
272273

274+
private bool CheckEnlistedTransactionsWereDisposed()
275+
{
276+
System.Transactions.Transaction current = System.Transactions.Transaction.Current;
277+
bool notAborted = ((System.Transactions.Transaction.Current?.TransactionInformation.Status) ?? System.Transactions.TransactionStatus.Aborted) != System.Transactions.TransactionStatus.Aborted;
278+
if (!notAborted) return true;
279+
280+
do
281+
{
282+
notAborted = current.TransactionInformation.Status != System.Transactions.TransactionStatus.Aborted;
283+
284+
try
285+
{
286+
current.Dispose();
287+
}
288+
catch (Exception ex)
289+
{
290+
log.Error("Error disposing enlisted transaction", ex);
291+
}
292+
293+
current = System.Transactions.Transaction.Current;
294+
} while (current != null && notAborted);
295+
296+
return false;
297+
}
298+
273299
private void Configure()
274300
{
275301
cfg = TestConfigurationHelper.GetDefaultConfiguration();

src/NHibernate/Transaction/AdoNetWithDistributedTransactionFactory.cs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public void ExecuteWorkInIsolation(ISessionImplementor session, IIsolatedWork wo
9090
}
9191
}
9292

93-
public class DistributedTransactionContext : ITransactionContext, IEnlistmentNotification
93+
public class DistributedTransactionContext : ITransactionContext, IEnlistmentNotification, IPromotableSinglePhaseNotification
9494
{
9595
public System.Transactions.Transaction AmbientTransation { get; set; }
9696
public bool ShouldCloseSessionOnDistributedTransactionCompleted { get; set; }
@@ -104,6 +104,79 @@ public DistributedTransactionContext(ISessionImplementor sessionImplementor, Sys
104104
IsInActiveTransaction = true;
105105
}
106106

107+
#region IPromotableSinglePhaseNotification Members
108+
109+
void IPromotableSinglePhaseNotification.Initialize()
110+
{
111+
logger.Error("promotable single-phase transaction initialize");
112+
using (new SessionIdLoggingContext(sessionImplementor.SessionId))
113+
{
114+
try
115+
{
116+
using (var tx = new TransactionScope(AmbientTransation))
117+
{
118+
sessionImplementor.BeforeTransactionCompletion(null);
119+
if (sessionImplementor.FlushMode != FlushMode.Manual && sessionImplementor.ConnectionManager.IsConnected)
120+
{
121+
using (sessionImplementor.ConnectionManager.FlushingFromDtcTransaction)
122+
{
123+
logger.Debug(string.Format("[session-id={0}] Flushing from Dtc Transaction", sessionImplementor.SessionId));
124+
sessionImplementor.Flush();
125+
}
126+
}
127+
logger.Debug("prepared for promotable single-phase transaction");
128+
129+
tx.Complete();
130+
}
131+
}
132+
catch (Exception exception)
133+
{
134+
logger.Error("promotable single-phase transaction prepare phase failed", exception);
135+
}
136+
}
137+
}
138+
139+
void IPromotableSinglePhaseNotification.SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment)
140+
{
141+
using (new SessionIdLoggingContext(sessionImplementor.SessionId))
142+
{
143+
try
144+
{
145+
sessionImplementor.BeforeTransactionCompletion(null);
146+
logger.Debug("committing promotable single-phase transaction");
147+
// we have nothing to do here, since it is the actual
148+
// DB connection that will commit the transaction
149+
singlePhaseEnlistment.Committed();
150+
IsInActiveTransaction = false;
151+
}
152+
catch (Exception exception)
153+
{
154+
logger.Error("promotable single-phase transaction prepare phase failed", exception);
155+
singlePhaseEnlistment.Aborted(exception);
156+
}
157+
}
158+
}
159+
160+
void IPromotableSinglePhaseNotification.Rollback(SinglePhaseEnlistment singlePhaseEnlistment)
161+
{
162+
using (new SessionIdLoggingContext(sessionImplementor.SessionId))
163+
{
164+
logger.Debug("rolled back promotable single-phase transaction");
165+
// Currently AfterTransactionCompletion is called by the handler for the TransactionCompleted event.
166+
//sessionImplementor.AfterTransactionCompletion(false, null);
167+
singlePhaseEnlistment.Done();
168+
IsInActiveTransaction = false;
169+
}
170+
}
171+
172+
byte[] ITransactionPromoter.Promote()
173+
{
174+
logger.Error("promotable single-phase transaction promote not implemented");
175+
throw new TransactionPromotionException("Not implemented");
176+
}
177+
178+
#endregion
179+
107180
#region IEnlistmentNotification Members
108181

109182
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
@@ -181,4 +254,4 @@ public void Dispose()
181254
}
182255
}
183256
}
184-
}
257+
}

0 commit comments

Comments
 (0)