namespace AcDream.App.Rendering; using System.Runtime.ExceptionServices; /// /// Reverse-order, all-attempted cleanup owner used while a composite resource /// is still under construction and after it becomes the aggregate owner. /// internal sealed class ResourceCleanupGroup : IRetryableResourceCleanup { private sealed record Entry(string Name, Action Release) { public bool Complete { get; set; } } private readonly List _entries = []; private bool _running; public bool IsCleanupComplete => _entries.All(static entry => entry.Complete); public void Add(string name, Action release) { ArgumentException.ThrowIfNullOrWhiteSpace(name); ArgumentNullException.ThrowIfNull(release); if (_running || IsCleanupComplete && _entries.Count != 0) throw new InvalidOperationException("The resource cleanup group is no longer accepting ownership."); _entries.Add(new Entry(name, release)); } public void TransferAll() { if (_running) throw new InvalidOperationException( "The resource cleanup group is currently releasing resources."); foreach (Entry entry in _entries) entry.Complete = true; } public void RetryCleanup() { if (_running || IsCleanupComplete) return; _running = true; List? failures = null; try { for (int i = _entries.Count - 1; i >= 0; i--) { Entry entry = _entries[i]; if (entry.Complete) continue; try { entry.Release(); entry.Complete = true; } catch (Exception failure) { (failures ??= []).Add(new InvalidOperationException( $"Resource cleanup operation '{entry.Name}' failed.", failure)); } } } finally { _running = false; } if (failures is not null) throw new AggregateException("Composite resource cleanup remains incomplete.", failures); } public void RollbackConstructionAndThrow( string message, Exception constructionFailure) { ArgumentException.ThrowIfNullOrWhiteSpace(message); ArgumentNullException.ThrowIfNull(constructionFailure); try { RetryCleanup(); } catch (Exception cleanupFailure) { throw new GlResourceConstructionException( message, this, [constructionFailure, cleanupFailure]); } ExceptionDispatchInfo.Capture(constructionFailure).Throw(); throw new InvalidOperationException("Unreachable construction rollback path."); } }