223 lines
7.0 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|