File: CliTransaction.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
 
namespace Microsoft.DotNet.Cli;
 
internal interface ITransactionContext
{
    public void AddRollbackAction(Action action);
 
    //  Add an action which will be run at the end of the transaction, whether it succeeds or not (after any rollback actions, if applicable)
    public void AddCleanupAction(Action action);
}
 
internal class CliTransaction
{
    //  Delegate called when rollback starts, mainly in order to print a log message
    public Action RollbackStarted { get; set; }
 
    //  Delegate called when rollback fails.  If set, exception will be passed to the delegate, but not rethrown
    public Action<Exception> RollbackFailed { get; set; }
 
    public static void RunNew(Action<ITransactionContext> action)
    {
        new CliTransaction().Run(action);
    }
 
    public void Run(Action<ITransactionContext> action, Action rollback)
    {
        Run(context =>
        {
            context.AddRollbackAction(rollback);
 
            action(context);
        });
    }
 
    public void Run(Action<ITransactionContext> action)
    {
        TransactionContext transactionContext = new();
        try
        {
            action(transactionContext);
        }
        catch (Exception)
        {
            //  Roll back transaction
            RollbackStarted?.Invoke();
 
            transactionContext.RollbackActions.Reverse();
            foreach (var rollbackAction in transactionContext.RollbackActions)
            {
                try
                {
                    rollbackAction();
                }
                catch (Exception ex)
                {
                    if (RollbackFailed != null)
                    {
                        RollbackFailed(ex);
                    }
                    else
                    {
                        throw;
                    }
                }
            }
 
            throw;
        }
        finally
        {
            foreach (var cleanupAction in transactionContext.CleanupActions)
            {
                cleanupAction();
            }
        }
    }
 
    class TransactionContext : ITransactionContext
    {
        public List<Action> RollbackActions { get; set; } = [];
 
        //  Actions which will be run either when the transaction completes successfully, or after rollback actions have been run
        public List<Action> CleanupActions { get; set; } = [];
 
        public void AddRollbackAction(Action action)
        {
            RollbackActions.Add(action);
        }
 
        public void AddCleanupAction(Action action)
        {
            CleanupActions.Add(action);
        }
    }
}
 
static class CliTransactionExtensions
{
    public static void Run(this ITransactionContext context, Action action, Action rollback = null, Action cleanup = null)
    {
        if (rollback != null)
        {
            context.AddRollbackAction(rollback);
        }
        if (cleanup != null)
        {
            context.AddCleanupAction(cleanup);
        }
        action();
    }
}