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

223 lines
7.0 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.Threading;
namespace System.Reactive.Linq.ObservableImpl
{
internal sealed class ConcatMany<T> : IObservable<T>
{
private readonly IObservable<IObservable<T>> _sources;
internal ConcatMany(IObservable<IObservable<T>> sources)
{
_sources = sources;
}
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
{
throw new ArgumentNullException(nameof(observer));
}
var parent = new ConcatManyOuterObserver(observer);
var d = _sources.SubscribeSafe(parent);
parent.OnSubscribe(d);
return parent;
}
internal sealed class ConcatManyOuterObserver : IObserver<IObservable<T>>, IDisposable
{
private readonly IObserver<T> _downstream;
private readonly ConcurrentQueue<IObservable<T>> _queue;
private readonly InnerObserver _innerObserver;
private SingleAssignmentDisposableValue _upstream;
private int _trampoline;
private Exception? _error;
private bool _done;
private int _active;
internal ConcatManyOuterObserver(IObserver<T> downstream)
{
_downstream = downstream;
_queue = new ConcurrentQueue<IObservable<T>>();
_innerObserver = new InnerObserver(this);
}
internal void OnSubscribe(IDisposable d)
{
_upstream.Disposable = d;
}
public void Dispose()
{
_innerObserver.Dispose();
DisposeMain();
}
private void DisposeMain()
{
_upstream.Dispose();
}
private bool IsDisposed()
{
return _upstream.IsDisposed;
}
public void OnCompleted()
{
Volatile.Write(ref _done, true);
Drain();
}
public void OnError(Exception error)
{
if (Interlocked.CompareExchange(ref _error, error, null) == null)
{
Volatile.Write(ref _done, true);
Drain();
}
}
public void OnNext(IObservable<T> value)
{
_queue.Enqueue(value);
Drain();
}
private void InnerNext(T item)
{
_downstream.OnNext(item);
}
private void InnerError(Exception error)
{
if (_innerObserver.Finish())
{
if (Interlocked.CompareExchange(ref _error, error, null) == null)
{
Volatile.Write(ref _done, true);
Volatile.Write(ref _active, 0);
Drain();
}
}
}
private void InnerComplete()
{
if (_innerObserver.Finish())
{
Volatile.Write(ref _active, 0);
Drain();
}
}
private void Drain()
{
if (Interlocked.Increment(ref _trampoline) != 1)
{
return;
}
do
{
if (IsDisposed())
{
while (_queue.TryDequeue(out _))
{
}
}
else
{
if (Volatile.Read(ref _active) == 0)
{
var isDone = Volatile.Read(ref _done);
if (isDone)
{
var ex = Volatile.Read(ref _error);
if (ex != null)
{
_downstream.OnError(ex);
DisposeMain();
continue;
}
}
if (_queue.TryDequeue(out var source))
{
var sad = new SingleAssignmentDisposable();
if (_innerObserver.SetDisposable(sad))
{
Interlocked.Exchange(ref _active, 1);
sad.Disposable = source.SubscribeSafe(_innerObserver);
}
}
else
{
if (isDone)
{
_downstream.OnCompleted();
DisposeMain();
}
}
}
}
} while (Interlocked.Decrement(ref _trampoline) != 0);
}
internal sealed class InnerObserver : IObserver<T>, IDisposable
{
private readonly ConcatManyOuterObserver _parent;
internal IDisposable? Upstream;
internal InnerObserver(ConcatManyOuterObserver parent)
{
_parent = parent;
}
internal bool SetDisposable(SingleAssignmentDisposable sad)
{
return Disposable.TrySetSingle(ref Upstream, sad) == TrySetSingleResult.Success;
}
internal bool Finish()
{
var sad = Volatile.Read(ref Upstream);
if (sad != BooleanDisposable.True)
{
if (Interlocked.CompareExchange(ref Upstream, null, sad) == sad)
{
sad!.Dispose(); // NB: Cannot be null when we get here; SetDisposable is called before Inner[Error|Completed] calls Finish.
return true;
}
}
return false;
}
public void Dispose()
{
Disposable.Dispose(ref Upstream);
}
public void OnCompleted() => _parent.InnerComplete();
public void OnError(Exception error) => _parent.InnerError(error);
public void OnNext(T value) => _parent.InnerNext(value);
}
}
}
}