@@ -24,10 +24,15 @@ internal enum ObserverState
2424/// <param name="logger"> The logger </param>
2525public abstract class BleObserver ( BleDevice device , ILogger < BleObserver > logger ) : IBleObserver , IAsyncDisposable
2626{
27+ private readonly record struct AdvertisementHandlerSubscription (
28+ Action < IGapAdvertisement > OnAdvertisement ,
29+ Action < Exception > ? OnError
30+ ) ;
31+
2732 private readonly BleDevice _bleDevice = device ;
2833 private readonly SemaphoreSlim _startStopSemaphore = new ( 1 , 1 ) ;
2934 private readonly Lock _handlersLock = new ( ) ;
30- private Action < IGapAdvertisement > [ ] _handlers = [ ] ;
35+ private AdvertisementHandlerSubscription [ ] _handlers = [ ] ;
3136
3237 private volatile ObserverState _observerState = ObserverState . Stopped ;
3338
@@ -103,25 +108,27 @@ public async Task StartObservingAsync(CancellationToken cancellationToken = defa
103108 }
104109
105110 /// <inheritdoc />
106- public IDisposable OnAdvertisement ( Action < IGapAdvertisement > onAdvertisement )
111+ public IDisposable OnAdvertisement ( Action < IGapAdvertisement > onAdvertisement , Action < Exception > ? onError = null )
107112 {
108113 ObjectDisposedException . ThrowIf ( _bleDevice . IsDisposing , nameof ( BleObserver ) ) ;
109114
115+ var subscription = new AdvertisementHandlerSubscription ( onAdvertisement , onError ) ;
116+
110117 // Extend handlers list
111118 lock ( _handlersLock )
112119 {
113- Action < IGapAdvertisement > [ ] oldHandlers = _handlers ;
114- var newArr = new Action < IGapAdvertisement > [ oldHandlers . Length + 1 ] ;
120+ AdvertisementHandlerSubscription [ ] oldHandlers = _handlers ;
121+ var newArr = new AdvertisementHandlerSubscription [ oldHandlers . Length + 1 ] ;
115122 Array . Copy ( oldHandlers , newArr , oldHandlers . Length ) ;
116- newArr [ ^ 1 ] = onAdvertisement ;
123+ newArr [ ^ 1 ] = subscription ;
117124 Volatile . Write ( ref _handlers , newArr ) ;
118125 }
119126
120127 return Disposable . Create (
121- ( this , onAdvertisement ) ,
128+ ( this , subscription ) ,
122129 static tuple =>
123130 {
124- ( BleObserver self , Action < IGapAdvertisement > handler ) = tuple ;
131+ ( BleObserver self , AdvertisementHandlerSubscription handler ) = tuple ;
125132 lock ( self . _handlersLock )
126133 {
127134 if ( Helpers . TryRemoveImmutable ( self . _handlers , handler , out var newHandlers ) )
@@ -143,12 +150,12 @@ protected void OnNext(IGapAdvertisement advertisement)
143150 // Taking the current snapshot of the handlers.
144151 // In case of an unsubscription of a handler we might have taken the reference here already and call it afterward.
145152 // This is a known tradeoff
146- Action < IGapAdvertisement > [ ] handlers = Volatile . Read ( ref _handlers ) ;
147- foreach ( Action < IGapAdvertisement > handler in handlers )
153+ AdvertisementHandlerSubscription [ ] handlers = Volatile . Read ( ref _handlers ) ;
154+ foreach ( AdvertisementHandlerSubscription handler in handlers )
148155 {
149156 try
150157 {
151- handler ( advertisement ) ;
158+ handler . OnAdvertisement ( advertisement ) ;
152159 }
153160 catch ( Exception e )
154161 {
@@ -158,6 +165,52 @@ protected void OnNext(IGapAdvertisement advertisement)
158165 }
159166 }
160167
168+ /// <summary> Notify subscribers of a fatal observation error. Existing subscriptions are terminated. </summary>
169+ /// <param name="exception"> The exception that triggered the error </param>
170+ protected async Task OnErrorAsync ( Exception exception )
171+ {
172+ ArgumentNullException . ThrowIfNull ( exception ) ;
173+
174+ if ( _bleDevice . IsDisposing )
175+ return ;
176+
177+ AdvertisementHandlerSubscription [ ] handlers ;
178+ await _startStopSemaphore . WaitAsync ( ) . ConfigureAwait ( false ) ;
179+ try
180+ {
181+ handlers = Volatile . Read ( ref _handlers ) ;
182+ if ( handlers . Length == 0 && _observerState is ObserverState . Stopped )
183+ return ;
184+
185+ _observerState = ObserverState . Stopped ;
186+ lock ( _handlersLock )
187+ {
188+ _handlers = [ ] ;
189+ }
190+ }
191+ finally
192+ {
193+ _startStopSemaphore . Release ( ) ;
194+ }
195+
196+ Logger . LogObservationFailed ( exception ) ;
197+
198+ foreach ( AdvertisementHandlerSubscription handler in handlers )
199+ {
200+ if ( handler . OnError is null )
201+ continue ;
202+
203+ try
204+ {
205+ handler . OnError ( exception ) ;
206+ }
207+ catch ( Exception e )
208+ {
209+ Logger . LogObservationErrorDuringErrorHandling ( e ) ;
210+ }
211+ }
212+ }
213+
161214 /// <summary> Core implementation to start observing async </summary>
162215 /// <param name="cancellationToken"> The cancellationToken to cancel the operation </param>
163216 /// <returns> A task that completes when observation has started </returns>
0 commit comments