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.");
}
}