refactor(net): own session wiring subscriptions

This commit is contained in:
Erik 2026-07-21 10:45:15 +02:00
parent 88fe1db37b
commit 7d452aa6a2
8 changed files with 577 additions and 61 deletions

View file

@ -0,0 +1,61 @@
namespace AcDream.Core.Net;
/// <summary>
/// Owns a transactionally-built set of subscriptions and releases them in
/// reverse registration order. Disposal is idempotent and attempts every
/// release even when one fails.
/// </summary>
internal sealed class SubscriptionSet : IDisposable
{
private List<IDisposable>? _subscriptions = [];
public void Add(IDisposable subscription)
{
ArgumentNullException.ThrowIfNull(subscription);
List<IDisposable>? subscriptions = _subscriptions;
if (subscriptions is null)
{
subscription.Dispose();
throw new ObjectDisposedException(nameof(SubscriptionSet));
}
subscriptions.Add(subscription);
}
public void Add(Action unsubscribe)
{
ArgumentNullException.ThrowIfNull(unsubscribe);
Add(new ActionSubscription(unsubscribe));
}
public void Dispose()
{
List<IDisposable>? subscriptions =
Interlocked.Exchange(ref _subscriptions, null);
if (subscriptions is null)
return;
List<Exception>? errors = null;
for (int i = subscriptions.Count - 1; i >= 0; i--)
{
try
{
subscriptions[i].Dispose();
}
catch (Exception error)
{
(errors ??= []).Add(error);
}
}
if (errors is not null)
throw new AggregateException("one or more subscriptions failed to detach", errors);
}
private sealed class ActionSubscription(Action unsubscribe) : IDisposable
{
private Action? _unsubscribe = unsubscribe;
public void Dispose() => Interlocked.Exchange(ref _unsubscribe, null)?.Invoke();
}
}