Kit.Core/LibExternal/System.Reactive/Linq/Observable/RetryWhen.cs

257 lines
7.7 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information.
using System.Collections.Concurrent;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using System.Threading;
namespace System.Reactive.Linq.ObservableImpl
{
internal sealed class RetryWhen<T, U> : IObservable<T>
{
private readonly IObservable<T> _source;
private readonly Func<IObservable<Exception>, IObservable<U>> _handler;
internal RetryWhen(IObservable<T> source, Func<IObservable<Exception>, IObservable<U>> handler)
{
_source = source;
_handler = handler;
}
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
{
throw new ArgumentNullException(nameof(observer));
}
var errorSignals = new Subject<Exception>();
IObservable<U> redo;
try
{
redo = _handler(errorSignals);
if (redo == null)
{
#pragma warning disable CA2201 // (Do not raise reserved exception types.) Backwards compatibility prevents us from complying.
throw new NullReferenceException("The handler returned a null IObservable");
#pragma warning restore CA2201
}
}
catch (Exception ex)
{
observer.OnError(ex);
return Disposable.Empty;
}
var parent = new MainObserver(observer, _source, new RedoSerializedObserver<Exception>(errorSignals));
var d = redo.SubscribeSafe(parent.HandlerConsumer);
parent.HandlerUpstream.Disposable = d;
parent.HandlerNext();
return parent;
}
private sealed class MainObserver : Sink<T>, IObserver<T>
{
private readonly IObservable<T> _source;
private readonly IObserver<Exception> _errorSignal;
internal readonly HandlerObserver HandlerConsumer;
private IDisposable? _upstream;
internal SingleAssignmentDisposableValue HandlerUpstream;
private int _trampoline;
private int _halfSerializer;
private Exception? _error;
internal MainObserver(IObserver<T> downstream, IObservable<T> source, IObserver<Exception> errorSignal) : base(downstream)
{
_source = source;
_errorSignal = errorSignal;
HandlerConsumer = new HandlerObserver(this);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Disposable.Dispose(ref _upstream);
HandlerUpstream.Dispose();
}
base.Dispose(disposing);
}
public void OnCompleted()
{
HalfSerializer.ForwardOnCompleted(this, ref _halfSerializer, ref _error);
}
public void OnError(Exception error)
{
if (Disposable.TrySetSerial(ref _upstream, null))
{
_errorSignal.OnNext(error);
}
}
public void OnNext(T value)
{
HalfSerializer.ForwardOnNext(this, value, ref _halfSerializer, ref _error);
}
private void HandlerError(Exception error)
{
HalfSerializer.ForwardOnError(this, error, ref _halfSerializer, ref _error);
}
private void HandlerComplete()
{
HalfSerializer.ForwardOnCompleted(this, ref _halfSerializer, ref _error);
}
internal void HandlerNext()
{
if (Interlocked.Increment(ref _trampoline) == 1)
{
do
{
var sad = new SingleAssignmentDisposable();
if (Disposable.TrySetSingle(ref _upstream, sad) != TrySetSingleResult.Success)
{
return;
}
sad.Disposable = _source.SubscribeSafe(this);
}
while (Interlocked.Decrement(ref _trampoline) != 0);
}
}
internal sealed class HandlerObserver : IObserver<U>
{
private readonly MainObserver _main;
internal HandlerObserver(MainObserver main)
{
_main = main;
}
public void OnCompleted()
{
_main.HandlerComplete();
}
public void OnError(Exception error)
{
_main.HandlerError(error);
}
public void OnNext(U value)
{
_main.HandlerNext();
}
}
}
}
internal sealed class RedoSerializedObserver<X> : IObserver<X>
{
#pragma warning disable CA2201 // (Do not raise reserved exception types.) This is a sentinel, and is not thrown, so there's no need for it to be anything else.
private static readonly Exception SignaledIndicator = new();
#pragma warning restore CA2201
private readonly IObserver<X> _downstream;
private readonly ConcurrentQueue<X> _queue;
private int _wip;
private Exception? _terminalException;
internal RedoSerializedObserver(IObserver<X> downstream)
{
_downstream = downstream;
_queue = new ConcurrentQueue<X>();
}
public void OnCompleted()
{
if (Interlocked.CompareExchange(ref _terminalException, ExceptionHelper.Terminated, null) == null)
{
Drain();
}
}
public void OnError(Exception error)
{
if (Interlocked.CompareExchange(ref _terminalException, error, null) == null)
{
Drain();
}
}
public void OnNext(X value)
{
_queue.Enqueue(value);
Drain();
}
private void Clear()
{
while (_queue.TryDequeue(out _))
{
}
}
private void Drain()
{
if (Interlocked.Increment(ref _wip) != 1)
{
return;
}
var missed = 1;
for (; ; )
{
var ex = Volatile.Read(ref _terminalException);
if (ex != null)
{
if (ex != SignaledIndicator)
{
Interlocked.Exchange(ref _terminalException, SignaledIndicator);
if (ex != ExceptionHelper.Terminated)
{
_downstream.OnError(ex);
}
else
{
_downstream.OnCompleted();
}
}
Clear();
}
else
{
while (_queue.TryDequeue(out var item))
{
_downstream.OnNext(item);
}
}
missed = Interlocked.Add(ref _wip, -missed);
if (missed == 0)
{
break;
}
}
}
}
}