Dreamine.Hybrid 1.0.3
Dreamine.Hybrid 프로젝트의 API와 구성 요소를 제공합니다.
로딩중...
검색중...
일치하는것 없음
InMemoryHybridMessageBus.cs
이 파일의 문서화 페이지로 가기
2using System;
3using System.Collections.Concurrent;
4using System.Collections.Generic;
5using System.Linq;
6using System.Threading;
7using System.Threading.Tasks;
8
10{
20 {
29 private readonly ConcurrentDictionary<Type, SubscriptionBucket> _subscriptions = new();
40
58 {
59 get => _exceptionHandler;
60 set => _exceptionHandler = value ?? throw new ArgumentNullException(nameof(value));
61 }
62
111 public async Task PublishAsync<TMessage>(
112 TMessage message,
113 CancellationToken cancellationToken = default)
114 where TMessage : IHybridMessage
115 {
116 if (message is null)
117 {
118 throw new ArgumentNullException(nameof(message));
119 }
120
121 if (!_subscriptions.TryGetValue(typeof(TMessage), out SubscriptionBucket? subscriptions))
122 {
123 return;
124 }
125
126 Subscription[] snapshot = subscriptions.CreateSnapshot();
127
128 foreach (Subscription subscription in snapshot)
129 {
130 if (cancellationToken.IsCancellationRequested)
131 {
132 return;
133 }
134
135 if (subscription.IsDisposed)
136 {
137 continue;
138 }
139
140 if (subscription.Handler is not Func<TMessage, CancellationToken, Task> handler)
141 {
142 continue;
143 }
144
145 try
146 {
147 await handler(message, cancellationToken).ConfigureAwait(false);
148 }
149 catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
150 {
151 return;
152 }
153 catch (ObjectDisposedException)
154 {
155 subscription.Dispose();
156 }
157 catch (InvalidOperationException)
158 {
159 subscription.Dispose();
160 }
161 catch (NullReferenceException)
162 {
163 /*
164 * Blazor Server / SignalR circuit가 아직 준비되지 않았거나,
165 * 이미 해제된 컴포넌트가 메시지를 수신할 때 발생할 수 있다.
166 *
167 * 메시지 버스는 한 구독자 실패 때문에 전체 publish를 중단하면 안 된다.
168 * 해당 구독은 더 이상 안전하지 않다고 보고 제거한다.
169 */
170 subscription.Dispose();
171 }
172 catch (Exception ex)
173 {
174 // Individual subscriber failure must not stop delivery to remaining subscribers.
175 // Delegate to the configured handler so callers can observe the failure
176 // (e.g. by wiring a logging-backed handler) without coupling this assembly
177 // to a concrete logger. Default handler is a no-op.
178 _exceptionHandler.Handle(ex, typeof(TMessage));
179 }
180 }
181 }
182
223 public IDisposable Subscribe<TMessage>(
224 Func<TMessage, CancellationToken, Task> handler)
225 where TMessage : IHybridMessage
226 {
227 if (handler is null)
228 {
229 throw new ArgumentNullException(nameof(handler));
230 }
231
232 Type messageType = typeof(TMessage);
233 var subscription = new Subscription(messageType, handler, Unsubscribe);
234
235 while (true)
236 {
237 SubscriptionBucket subscriptions = _subscriptions.GetOrAdd(
238 messageType,
239 _ => new SubscriptionBucket());
240
241 if (subscriptions.TryAdd(subscription))
242 {
243 return subscription;
244 }
245
246 _subscriptions.TryRemove(
247 new KeyValuePair<Type, SubscriptionBucket>(messageType, subscriptions));
248 }
249 }
250
267 private void Unsubscribe(Subscription subscription)
268 {
269 if (subscription is null)
270 {
271 return;
272 }
273
274 if (!_subscriptions.TryGetValue(subscription.MessageType, out SubscriptionBucket? subscriptions))
275 {
276 return;
277 }
278
279 subscriptions.Remove(subscription);
280
281 if (subscriptions.TryCloseIfEmpty())
282 {
283 _subscriptions.TryRemove(
284 new KeyValuePair<Type, SubscriptionBucket>(subscription.MessageType, subscriptions));
285 }
286 }
287
296 private sealed class SubscriptionBucket
297 {
306 private readonly List<Subscription> _subscriptions = new();
315 private bool _closed;
316
341 public bool TryAdd(Subscription subscription)
342 {
343 lock (_subscriptions)
344 {
345 if (_closed)
346 {
347 return false;
348 }
349
350 _subscriptions.Add(subscription);
351 return true;
352 }
353 }
354
372 {
373 lock (_subscriptions)
374 {
375 return _subscriptions.ToArray();
376 }
377 }
378
395 public void Remove(Subscription subscription)
396 {
397 lock (_subscriptions)
398 {
399 _subscriptions.Remove(subscription);
400 }
401 }
402
419 public bool TryCloseIfEmpty()
420 {
421 lock (_subscriptions)
422 {
423 if (_subscriptions.Count != 0)
424 {
425 return false;
426 }
427
428 _closed = true;
429 return true;
430 }
431 }
432 }
433
442 private sealed class Subscription : IDisposable
443 {
452 private readonly Action<Subscription> _unsubscribe;
461 private volatile bool _disposed;
462
504 Type messageType,
505 object handler,
506 Action<Subscription> unsubscribe)
507 {
508 MessageType = messageType ?? throw new ArgumentNullException(nameof(messageType));
509 Handler = handler ?? throw new ArgumentNullException(nameof(handler));
510 _unsubscribe = unsubscribe ?? throw new ArgumentNullException(nameof(unsubscribe));
511 }
512
521 public Type MessageType { get; }
522
531 public object Handler { get; }
532
541 public bool IsDisposed => _disposed;
542
551 public void Dispose()
552 {
553 if (_disposed)
554 {
555 return;
556 }
557
558 _disposed = true;
559 _unsubscribe(this);
560 }
561 }
562 }
563}
volatile IHybridMessageBusExceptionHandler _exceptionHandler
IDisposable Subscribe< TMessage >(Func< TMessage, CancellationToken, Task > handler)
async Task PublishAsync< TMessage >(TMessage message, CancellationToken cancellationToken=default)
readonly ConcurrentDictionary< Type, SubscriptionBucket > _subscriptions
Subscription(Type messageType, object handler, Action< Subscription > unsubscribe)