using System.Collections.Concurrent; using System.Threading.Channels; namespace NpgsqlRest; public class Broadcaster { private readonly ConcurrentDictionary> _channels = new(); public void Broadcast(T message) { foreach (var kvp in _channels) { var writer = kvp.Value.Writer; if (!writer.TryWrite(message)) { // Channel is closed, remove it _channels.TryRemove(kvp.Key, out _); } } } public ChannelReader Subscribe(Guid subscriberId) { if (_channels.TryRemove(subscriberId, out var existingChannel)) { existingChannel.Writer.TryComplete(); } var channel = Channel.CreateUnbounded(); _channels[subscriberId] = channel; return channel.Reader; } public void Unsubscribe(Guid subscriberId) { if (_channels.TryRemove(subscriberId, out var channel)) { channel.Writer.TryComplete(); } } public void CompleteAll() { foreach (var kvp in _channels) { kvp.Value.Writer.TryComplete(); } _channels.Clear(); } /// /// Number of currently subscribed channels. Used by integration tests to wait until an SSE /// subscriber has registered before triggering a publish, avoiding a race between /// Subscribe and the test's HTTP call. Cheap on a . /// public int SubscriberCount => _channels.Count; }