From 165c9c6e58ed43acc89134729c3197cd1da2ddc9 Mon Sep 17 00:00:00 2001 From: Theodore Tsirpanis Date: Thu, 29 Sep 2022 23:59:58 +0300 Subject: [PATCH] Support trimming distributed transactions if unused, without a feature switch. --- .../ref/System.Transactions.Local.cs | 2 +- .../DtcProxyShim/DtcProxyShimFactory.cs | 49 +++++++++++++++++-- .../System/Transactions/TransactionManager.cs | 33 ++++++++++++- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/libraries/System.Transactions.Local/ref/System.Transactions.Local.cs b/src/libraries/System.Transactions.Local/ref/System.Transactions.Local.cs index 8f92c7c0f412b..6bede5c346a88 100644 --- a/src/libraries/System.Transactions.Local/ref/System.Transactions.Local.cs +++ b/src/libraries/System.Transactions.Local/ref/System.Transactions.Local.cs @@ -191,7 +191,7 @@ public static partial class TransactionManager [System.Diagnostics.CodeAnalysis.DisallowNullAttribute] public static System.Transactions.HostCurrentTransactionCallback? HostCurrentCallback { get { throw null; } set { } } public static System.TimeSpan MaximumTimeout { get { throw null; } set { } } - public static bool ImplicitDistributedTransactions { get; set; } + public static bool ImplicitDistributedTransactions { get; [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCode("Distributed transactions support may not be compatible with trimming. If your program creates a distributed transaction via System.Transactions, the correctness of the application cannot be guaranteed after trimming.")] set; } public static event System.Transactions.TransactionStartedEventHandler? DistributedTransactionStarted { add { } remove { } } public static void RecoveryComplete(System.Guid resourceManagerIdentifier) { } public static System.Transactions.Enlistment Reenlist(System.Guid resourceManagerIdentifier, byte[] recoveryInformation, System.Transactions.IEnlistmentNotification enlistmentNotification) { throw null; } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcProxyShimFactory.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcProxyShimFactory.cs index cf5cb38fc4121..572f0bb497812 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcProxyShimFactory.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/DtcProxyShim/DtcProxyShimFactory.cs @@ -21,6 +21,11 @@ internal sealed class DtcProxyShimFactory // at the same time. private static readonly object _proxyInitLock = new(); + // This object will perform the actual distributed transaction connection. + // It will be set only if TransactionManager.ImplicitDefaultTransactions + // is set to true, allowing the relevant code to be trimmed otherwise. + internal static ITransactionConnector? s_transactionConnector; + // Lock to protect access to listOfNotifications. private readonly object _notificationLock = new(); @@ -39,6 +44,7 @@ internal DtcProxyShimFactory(EventWaitHandle notificationEventHandle) // https://docs.microsoft.com/previous-versions/windows/desktop/ms678898(v=vs.85) [DllImport(Interop.Libraries.Xolehlp, CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] + [RequiresUnreferencedCode(TransactionManager.DistributedTransactionTrimmingWarning)] private static extern void DtcGetTransactionManagerExW( [MarshalAs(UnmanagedType.LPWStr)] string? pszHost, [MarshalAs(UnmanagedType.LPWStr)] string? pszTmName, @@ -47,7 +53,7 @@ private static extern void DtcGetTransactionManagerExW( object? pvConfigPararms, [MarshalAs(UnmanagedType.Interface)] out ITransactionDispenser ppvObject); - [RequiresUnreferencedCode("Distributed transactions support may not be compatible with trimming. If your program creates a distributed transaction via System.Transactions, the correctness of the application cannot be guaranteed after trimming.")] + [RequiresUnreferencedCode(TransactionManager.DistributedTransactionTrimmingWarning)] private static void DtcGetTransactionManager(string? nodeName, out ITransactionDispenser localDispenser) => DtcGetTransactionManagerExW(nodeName, null, Guids.IID_ITransactionDispenser_Guid, 0, null, out localDispenser); @@ -64,14 +70,15 @@ public void ConnectToProxy( throw new PlatformNotSupportedException(SR.DistributedNotSupportedOn32Bits); } - if (!TransactionManager.ImplicitDistributedTransactions) + if (s_transactionConnector is null) { throw new NotSupportedException(SR.ImplicitDistributedTransactionsDisabled); } - ConnectToProxyCore(nodeName, resourceManagerIdentifier, managedIdentifier, out nodeNameMatches, out whereabouts, out resourceManagerShim); + s_transactionConnector.ConnectToProxyCore(this, nodeName, resourceManagerIdentifier, managedIdentifier, out nodeNameMatches, out whereabouts, out resourceManagerShim); } + [RequiresUnreferencedCode(TransactionManager.DistributedTransactionTrimmingWarning)] private void ConnectToProxyCore( string? nodeName, Guid resourceManagerIdentifier, @@ -82,9 +89,7 @@ private void ConnectToProxyCore( { lock (_proxyInitLock) { -#pragma warning disable IL2026 // This warning is left in the product so developers get an ILLink warning when trimming an app using this transaction support DtcGetTransactionManager(nodeName, out ITransactionDispenser? localDispenser); -#pragma warning restore IL2026 // Check to make sure the node name matches. if (nodeName is not null) @@ -371,4 +376,38 @@ internal ITransactionReceiver GetCachedReceiver() internal void ReturnCachedReceiver(ITransactionReceiver receiver) => _cachedReceivers.Enqueue(receiver); + + internal interface ITransactionConnector + { + void ConnectToProxyCore( + DtcProxyShimFactory proxyShimFactory, + string? nodeName, + Guid resourceManagerIdentifier, + object managedIdentifier, + out bool nodeNameMatches, + out byte[] whereabouts, + out ResourceManagerShim resourceManagerShim); + } + + [RequiresUnreferencedCode(TransactionManager.DistributedTransactionTrimmingWarning)] + internal sealed class DtcTransactionConnector : ITransactionConnector + { + public void ConnectToProxyCore( + DtcProxyShimFactory proxyShimFactory, + string? nodeName, + Guid resourceManagerIdentifier, + object managedIdentifier, + out bool nodeNameMatches, + out byte[] whereabouts, + out ResourceManagerShim resourceManagerShim) + { + proxyShimFactory.ConnectToProxyCore( + nodeName, + resourceManagerIdentifier, + managedIdentifier, + out nodeNameMatches, + out whereabouts, + out resourceManagerShim); + } + } } diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs index 02ebc9e29b4f5..353cd62be8263 100644 --- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs +++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs @@ -6,6 +6,9 @@ using System.IO; using System.Threading; using System.Transactions.Configuration; +#if WINDOWS +using System.Transactions.DtcProxyShim; +#endif using System.Transactions.Oletx; namespace System.Transactions @@ -29,6 +32,10 @@ public static class TransactionManager private static TransactionTable? s_transactionTable; private static TransactionStartedEventHandler? s_distributedTransactionStartedDelegate; + + internal const string DistributedTransactionTrimmingWarning = + "Distributed transactions support may not be compatible with trimming. If your program creates a distributed transaction via System.Transactions, the correctness of the application cannot be guaranteed after trimming."; + public static event TransactionStartedEventHandler? DistributedTransactionStarted { add @@ -391,7 +398,31 @@ public static TimeSpan MaximumTimeout } } - public static bool ImplicitDistributedTransactions { get; set; } +#if WINDOWS + public static bool ImplicitDistributedTransactions + { + get => DtcProxyShimFactory.s_transactionConnector is not null; + [RequiresUnreferencedCode(DistributedTransactionTrimmingWarning)] + set + { + if (value) + { + DtcProxyShimFactory.s_transactionConnector ??= new DtcProxyShimFactory.DtcTransactionConnector(); + } + else + { + DtcProxyShimFactory.s_transactionConnector = null; + } + } + } +#else + public static bool ImplicitDistributedTransactions + { + get; + [RequiresUnreferencedCode(DistributedTransactionTrimmingWarning)] + set; + } +#endif // This routine writes the "header" for the recovery information, based on the // type of the calling object and its provided parameter collection. This information