File: TransactionalAction.cs
Web Access
Project: ..\..\..\src\Cli\dotnet\dotnet.csproj (dotnet)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Reflection;
using System.Transactions;
using Microsoft.DotNet.Cli.Utils;
 
namespace Microsoft.DotNet.Cli;
 
public sealed class TransactionalAction
{
    static TransactionalAction()
    {
        DisableTransactionTimeoutUpperLimit();
    }
 
    private class EnlistmentNotification(Action commit, Action rollback) : IEnlistmentNotification
    {
        private Action _commit = commit;
        private Action _rollback = rollback;
 
        public void Commit(Enlistment enlistment)
        {
            if (_commit != null)
            {
                _commit();
                _commit = null;
            }
 
            enlistment.Done();
        }
 
        public void InDoubt(Enlistment enlistment)
        {
            Rollback(enlistment);
        }
 
        public void Prepare(PreparingEnlistment enlistment)
        {
            enlistment.Prepared();
        }
 
        public void Rollback(Enlistment enlistment)
        {
            if (_rollback != null)
            {
                _rollback();
                _rollback = null;
            }
 
            enlistment.Done();
        }
    }
 
    public static T Run<T>(
        Func<T> action,
        Action commit = null,
        Action rollback = null)
    {
        if (action == null)
        {
            throw new ArgumentNullException(nameof(action));
        }
 
        // This automatically inherits any ambient transaction
        // If a transaction is inherited, completing this scope will be a no-op
        T result = default(T);
        try
        {
            using (var scope = new TransactionScope(
                TransactionScopeOption.Required,
                TimeSpan.Zero))
            {
                Transaction.Current.EnlistVolatile(
                    new EnlistmentNotification(commit, rollback),
                    EnlistmentOptions.None);
 
                result = action();
 
                scope.Complete();
            }
 
            return result;
        }
        catch (TransactionAbortedException ex)
        {
            Reporter.Verbose.WriteLine(string.Format("TransactionAbortedException Message: {0}", ex.Message));
            Reporter.Verbose.WriteLine(
                $"Inner Exception Message: {ex?.InnerException?.Message + "---" + ex?.InnerException}");
            throw;
        }
    }
 
    private static void SetTransactionManagerField(string fieldName, object value)
    {
        typeof(TransactionManager).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static)
            .SetValue(null, value);
    }
 
    // https://github.com/dotnet/sdk/issues/21101
    // we should use the proper API once it is available
    public static void DisableTransactionTimeoutUpperLimit()
    {
        SetTransactionManagerField("s_cachedMaxTimeout", true);
        SetTransactionManagerField("s_maximumTimeout", TimeSpan.Zero);
    }
 
    public static void Run(
        Action action,
        Action commit = null,
        Action rollback = null)
    {
        Run<object>(
            action: () =>
            {
                action();
                return null;
            },
            commit: commit,
            rollback: rollback);
    }
}