Skip to content

Commit c94e4ff

Browse files
committed
Add test coverage and fix non-Windows TransactionInterop
Notable end-to-end testing for the complex recovery and promotion flows.
1 parent 34608fa commit c94e4ff

34 files changed

+632
-171
lines changed

src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
<Compile Include="System\Transactions\TransactionInterop.cs" />
100100
</ItemGroup>
101101
<ItemGroup Condition="'$(TargetPlatformIdentifier)' != 'windows'">
102+
<Compile Include="System\Transactions\TransactionInteropNonWindows.cs" />
102103
<Compile Include="System\Transactions\NonWindowsUnsupported.cs" />
103104
</ItemGroup>
104105
<ItemGroup>

src/libraries/System.Transactions.Local/src/System/Transactions/NonWindowsUnsupported.cs

Lines changed: 16 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,16 @@ internal OletxCommittableTransaction CreateTransaction(TransactionOptions option
3131
internal void ResourceManagerRecoveryComplete(Guid resourceManagerIdentifier)
3232
=> throw NotSupported();
3333

34-
internal byte[] GetWhereabouts()
34+
internal static byte[] GetWhereabouts()
3535
=> throw NotSupported();
3636

37-
internal Transaction GetTransactionFromDtcTransaction(IDtcTransaction transactionNative)
37+
internal static Transaction GetTransactionFromDtcTransaction(IDtcTransaction transactionNative)
3838
=> throw NotSupported();
3939

40-
internal OletxTransaction GetTransactionFromExportCookie(byte[] cookie, Guid txId)
40+
internal static OletxTransaction GetTransactionFromExportCookie(byte[] cookie, Guid txId)
4141
=> throw NotSupported();
4242

43-
internal OletxTransaction GetOletxTransactionFromTransmitterPropagationToken(byte[] propagationToken)
43+
internal static OletxTransaction GetOletxTransactionFromTransmitterPropagationToken(byte[] propagationToken)
4444
=> throw NotSupported();
4545

4646
internal static Exception NotSupported()
@@ -105,12 +105,17 @@ internal IPromotedEnlistment EnlistVolatile(
105105
EnlistmentOptions enlistmentOptions)
106106
=> throw NotSupported();
107107

108+
internal static byte[] GetExportCookie(byte[] whereaboutsCopy)
109+
=> throw NotSupported();
110+
108111
public object GetRealObject(StreamingContext context)
109112
=> throw NotSupported();
110113

111-
internal void Dispose()
112-
{
113-
}
114+
internal static byte[] GetTransmitterPropagationToken()
115+
=> throw NotSupported();
116+
117+
internal static IDtcTransaction GetDtcTransaction()
118+
=> throw NotSupported();
114119

115120
void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingContext context)
116121
{
@@ -124,6 +129,10 @@ void ISerializable.GetObjectData(SerializationInfo serializationInfo, StreamingC
124129
throw new PlatformNotSupportedException();
125130
}
126131

132+
internal void Dispose()
133+
{
134+
}
135+
127136
internal static Exception NotSupported()
128137
=> new PlatformNotSupportedException(SR.DistributedNotSupported);
129138

@@ -143,57 +152,3 @@ internal sealed class OletxCommittableTransaction : OletxTransaction
143152
internal void BeginCommit(InternalTransaction tx) => throw NotSupported();
144153
}
145154
}
146-
147-
namespace System.Transactions
148-
{
149-
public static class TransactionInterop
150-
{
151-
internal static OletxTransaction ConvertToOletxTransaction(Transaction transaction)
152-
=> throw NotSupported();
153-
154-
/// <summary>
155-
/// This is the PromoterType value that indicates that the transaction is promoting to MSDTC.
156-
///
157-
/// If using the variation of Transaction.EnlistPromotableSinglePhase that takes a PromoterType and the
158-
/// ITransactionPromoter being used promotes to MSDTC, then this is the value that should be
159-
/// specified for the PromoterType parameter to EnlistPromotableSinglePhase.
160-
///
161-
/// If using the variation of Transaction.EnlistPromotableSinglePhase that assumes promotion to MSDTC and
162-
/// it that returns false, the caller can compare this value with Transaction.PromoterType to
163-
/// verify that the transaction promoted, or will promote, to MSDTC. If the Transaction.PromoterType
164-
/// matches this value, then the caller can continue with its enlistment with MSDTC. But if it
165-
/// does not match, the caller will not be able to enlist with MSDTC.
166-
/// </summary>
167-
public static readonly Guid PromoterTypeDtc = new Guid("14229753-FFE1-428D-82B7-DF73045CB8DA");
168-
169-
public static byte[] GetExportCookie(Transaction transaction, byte[] whereabouts)
170-
=> throw NotSupported();
171-
172-
public static Transaction GetTransactionFromExportCookie(byte[] cookie)
173-
=> throw NotSupported();
174-
175-
public static byte[] GetTransmitterPropagationToken(Transaction transaction)
176-
=> throw NotSupported();
177-
178-
internal static byte[] GetTransmitterPropagationToken(OletxTransaction oletxTx)
179-
=> throw NotSupported();
180-
181-
public static Transaction GetTransactionFromTransmitterPropagationToken(byte[] propagationToken)
182-
=> throw NotSupported();
183-
184-
public static IDtcTransaction GetDtcTransaction(Transaction transaction)
185-
=> throw NotSupported();
186-
187-
public static Transaction GetTransactionFromDtcTransaction(IDtcTransaction transactionNative)
188-
=> throw NotSupported();
189-
190-
public static byte[] GetWhereabouts()
191-
=> throw NotSupported();
192-
193-
internal static OletxTransaction GetOletxTransactionFromTransmitterPropagationToken(byte[] propagationToken)
194-
=> throw NotSupported();
195-
196-
internal static Exception NotSupported()
197-
=> new PlatformNotSupportedException(SR.DistributedNotSupported);
198-
}
199-
}
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Runtime.InteropServices;
5+
using System.Transactions.Oletx;
6+
7+
namespace System.Transactions
8+
{
9+
public static class TransactionInterop
10+
{
11+
internal static OletxTransaction ConvertToOletxTransaction(Transaction transaction)
12+
{
13+
ArgumentNullException.ThrowIfNull(transaction);
14+
15+
ObjectDisposedException.ThrowIf(transaction.Disposed, transaction);
16+
17+
if (transaction._complete)
18+
{
19+
throw TransactionException.CreateTransactionCompletedException(transaction.DistributedTxId);
20+
}
21+
22+
OletxTransaction? distributedTx = transaction.Promote();
23+
if (distributedTx == null)
24+
{
25+
throw OletxTransaction.NotSupported();
26+
}
27+
return distributedTx;
28+
}
29+
30+
/// <summary>
31+
/// This is the PromoterType value that indicates that the transaction is promoting to MSDTC.
32+
///
33+
/// If using the variation of Transaction.EnlistPromotableSinglePhase that takes a PromoterType and the
34+
/// ITransactionPromoter being used promotes to MSDTC, then this is the value that should be
35+
/// specified for the PromoterType parameter to EnlistPromotableSinglePhase.
36+
///
37+
/// If using the variation of Transaction.EnlistPromotableSinglePhase that assumes promotion to MSDTC and
38+
/// it that returns false, the caller can compare this value with Transaction.PromoterType to
39+
/// verify that the transaction promoted, or will promote, to MSDTC. If the Transaction.PromoterType
40+
/// matches this value, then the caller can continue with its enlistment with MSDTC. But if it
41+
/// does not match, the caller will not be able to enlist with MSDTC.
42+
/// </summary>
43+
public static readonly Guid PromoterTypeDtc = new Guid("14229753-FFE1-428D-82B7-DF73045CB8DA");
44+
45+
public static byte[] GetExportCookie(Transaction transaction, byte[] whereabouts)
46+
{
47+
ArgumentNullException.ThrowIfNull(transaction);
48+
ArgumentNullException.ThrowIfNull(whereabouts);
49+
50+
TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
51+
if (etwLog.IsEnabled())
52+
{
53+
etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetExportCookie");
54+
}
55+
56+
// Copy the whereabouts so that it cannot be modified later.
57+
var whereaboutsCopy = new byte[whereabouts.Length];
58+
Buffer.BlockCopy(whereabouts, 0, whereaboutsCopy, 0, whereabouts.Length);
59+
60+
ConvertToOletxTransaction(transaction);
61+
byte[] cookie = OletxTransaction.GetExportCookie(whereaboutsCopy);
62+
63+
if (etwLog.IsEnabled())
64+
{
65+
etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetExportCookie");
66+
}
67+
68+
return cookie;
69+
}
70+
71+
public static Transaction GetTransactionFromExportCookie(byte[] cookie)
72+
{
73+
ArgumentNullException.ThrowIfNull(cookie);
74+
75+
if (cookie.Length < 32)
76+
{
77+
throw new ArgumentException(SR.InvalidArgument, nameof(cookie));
78+
}
79+
80+
TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
81+
if (etwLog.IsEnabled())
82+
{
83+
etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromExportCookie");
84+
}
85+
86+
var cookieCopy = new byte[cookie.Length];
87+
Buffer.BlockCopy(cookie, 0, cookieCopy, 0, cookie.Length);
88+
cookie = cookieCopy;
89+
90+
// Extract the transaction guid from the propagation token to see if we already have a
91+
// transaction object for the transaction.
92+
// In a cookie, the transaction guid is preceded by a signature guid.
93+
var txId = new Guid(cookie.AsSpan(16, 16));
94+
95+
// First check to see if there is a promoted LTM transaction with the same ID. If there
96+
// is, just return that.
97+
Transaction? transaction = TransactionManager.FindPromotedTransaction(txId);
98+
if (transaction != null)
99+
{
100+
if (etwLog.IsEnabled())
101+
{
102+
etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromExportCookie");
103+
}
104+
105+
return transaction;
106+
}
107+
108+
// Find or create the promoted transaction.
109+
OletxTransaction dTx = OletxTransactionManager.GetTransactionFromExportCookie(cookieCopy, txId);
110+
transaction = TransactionManager.FindOrCreatePromotedTransaction(txId, dTx);
111+
112+
if (etwLog.IsEnabled())
113+
{
114+
etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromExportCookie");
115+
}
116+
117+
return transaction;
118+
}
119+
120+
public static byte[] GetTransmitterPropagationToken(Transaction transaction)
121+
{
122+
ArgumentNullException.ThrowIfNull(transaction);
123+
124+
TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
125+
if (etwLog.IsEnabled())
126+
{
127+
etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransmitterPropagationToken");
128+
}
129+
130+
ConvertToOletxTransaction(transaction);
131+
byte[] token = OletxTransaction.GetTransmitterPropagationToken();
132+
133+
if (etwLog.IsEnabled())
134+
{
135+
etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransmitterPropagationToken");
136+
}
137+
138+
return token;
139+
}
140+
141+
public static Transaction GetTransactionFromTransmitterPropagationToken(byte[] propagationToken)
142+
{
143+
ArgumentNullException.ThrowIfNull(propagationToken);
144+
145+
if (propagationToken.Length < 24)
146+
{
147+
throw new ArgumentException(SR.InvalidArgument, nameof(propagationToken));
148+
}
149+
150+
TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
151+
if (etwLog.IsEnabled())
152+
{
153+
etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromTransmitterPropagationToken");
154+
}
155+
156+
// Extract the transaction guid from the propagation token to see if we already have a
157+
// transaction object for the transaction.
158+
// In a propagation token, the transaction guid is preceded by two version DWORDs.
159+
var txId = new Guid(propagationToken.AsSpan(8, 16));
160+
161+
// First check to see if there is a promoted LTM transaction with the same ID. If there is, just return that.
162+
Transaction? tx = TransactionManager.FindPromotedTransaction(txId);
163+
if (null != tx)
164+
{
165+
if (etwLog.IsEnabled())
166+
{
167+
etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromTransmitterPropagationToken");
168+
}
169+
170+
return tx;
171+
}
172+
173+
OletxTransaction dTx = GetOletxTransactionFromTransmitterPropagationToken(propagationToken);
174+
175+
// If a transaction is found then FindOrCreate will Dispose the distributed transaction created.
176+
Transaction returnValue = TransactionManager.FindOrCreatePromotedTransaction(txId, dTx);
177+
178+
if (etwLog.IsEnabled())
179+
{
180+
etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromTransmitterPropagationToken");
181+
}
182+
return returnValue;
183+
}
184+
185+
public static IDtcTransaction GetDtcTransaction(Transaction transaction)
186+
{
187+
ArgumentNullException.ThrowIfNull(transaction);
188+
189+
TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
190+
if (etwLog.IsEnabled())
191+
{
192+
etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetDtcTransaction");
193+
}
194+
195+
ConvertToOletxTransaction(transaction);
196+
IDtcTransaction transactionNative = OletxTransaction.GetDtcTransaction();
197+
198+
if (etwLog.IsEnabled())
199+
{
200+
etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetDtcTransaction");
201+
}
202+
203+
return transactionNative;
204+
}
205+
206+
public static Transaction GetTransactionFromDtcTransaction(IDtcTransaction transactionNative)
207+
{
208+
ArgumentNullException.ThrowIfNull(transactionNative);
209+
210+
TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
211+
if (etwLog.IsEnabled())
212+
{
213+
etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromDtcTransaction");
214+
}
215+
216+
Transaction transaction = OletxTransactionManager.GetTransactionFromDtcTransaction(transactionNative);
217+
218+
if (etwLog.IsEnabled())
219+
{
220+
etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetTransactionFromDtcTransaction");
221+
}
222+
return transaction;
223+
}
224+
225+
public static byte[] GetWhereabouts()
226+
{
227+
TransactionsEtwProvider etwLog = TransactionsEtwProvider.Log;
228+
if (etwLog.IsEnabled())
229+
{
230+
etwLog.MethodEnter(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetWhereabouts");
231+
}
232+
233+
byte[] returnValue = OletxTransactionManager.GetWhereabouts();
234+
235+
if (etwLog.IsEnabled())
236+
{
237+
etwLog.MethodExit(TraceSourceType.TraceSourceOleTx, "TransactionInterop.GetWhereabouts");
238+
}
239+
return returnValue;
240+
}
241+
242+
internal static OletxTransaction GetOletxTransactionFromTransmitterPropagationToken(byte[] propagationToken)
243+
{
244+
ArgumentNullException.ThrowIfNull(propagationToken);
245+
246+
if (propagationToken.Length < 24)
247+
{
248+
throw new ArgumentException(SR.InvalidArgument, nameof(propagationToken));
249+
}
250+
251+
byte[] propagationTokenCopy = new byte[propagationToken.Length];
252+
Array.Copy(propagationToken, propagationTokenCopy, propagationToken.Length);
253+
254+
return OletxTransactionManager.GetOletxTransactionFromTransmitterPropagationToken(propagationTokenCopy);
255+
}
256+
}
257+
}

0 commit comments

Comments
 (0)