|  | @@ -2,7 +2,6 @@
 | 
	
		
			
				|  |  |  // The .NET Foundation licenses this file to you under the Apache 2.0 License.
 | 
	
		
			
				|  |  |  // See the LICENSE file in the project root for more information. 
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -using System.Threading;
 | 
	
		
			
				|  |  |  using System.Threading.Tasks;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  namespace System.Reactive.Linq
 | 
	
	
		
			
				|  | @@ -28,33 +27,176 @@ namespace System.Reactive.Linq
 | 
	
		
			
				|  |  |          private sealed class AutoDetachAsyncObserver : AsyncObserverBase<T>, IAsyncDisposable
 | 
	
		
			
				|  |  |          {
 | 
	
		
			
				|  |  |              private readonly IAsyncObserver<T> _observer;
 | 
	
		
			
				|  |  | +            private readonly object _gate = new object();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            private IAsyncDisposable _subscription;
 | 
	
		
			
				|  |  | +            private Task _task;
 | 
	
		
			
				|  |  | +            private bool _disposing;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              public AutoDetachAsyncObserver(IAsyncObserver<T> observer)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  |                  _observer = observer;
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            public Task AssignAsync(IAsyncDisposable subscription)
 | 
	
		
			
				|  |  | +            public async Task AssignAsync(IAsyncDisposable subscription)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                throw new NotImplementedException();
 | 
	
		
			
				|  |  | +                var shouldDispose = false;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                lock (_gate)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    if (_disposing)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        shouldDispose = true;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                    else
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        _subscription = subscription;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if (shouldDispose)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    await subscription.DisposeAsync().ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            public Task DisposeAsync()
 | 
	
		
			
				|  |  | +            public async Task DisposeAsync()
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                throw new NotImplementedException();
 | 
	
		
			
				|  |  | +                var task = default(Task);
 | 
	
		
			
				|  |  | +                var subscription = default(IAsyncDisposable);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                lock (_gate)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    //
 | 
	
		
			
				|  |  | +                    // NB: The postcondition of awaiting the first DisposeAsync call to complete is that all message
 | 
	
		
			
				|  |  | +                    //     processing has ceased, i.e. no further On*AsyncCore calls will be made. This is achieved
 | 
	
		
			
				|  |  | +                    //     here by setting _disposing to true, which is checked by the On*AsyncCore calls upon
 | 
	
		
			
				|  |  | +                    //     entry, and by awaiting the task of any in-flight On*AsyncCore calls.
 | 
	
		
			
				|  |  | +                    //
 | 
	
		
			
				|  |  | +                    //     Timing of the disposal of the subscription is less deterministic due to the intersection
 | 
	
		
			
				|  |  | +                    //     with the AssignAsync code path. However, the auto-detach observer can only be returned
 | 
	
		
			
				|  |  | +                    //     from the SubscribeAsync call *after* a call to AssignAsync has been made and awaited, so
 | 
	
		
			
				|  |  | +                    //     either AssignAsync triggers the disposal and an already disposed instance is returned, or
 | 
	
		
			
				|  |  | +                    //     the user calling DisposeAsync will either encounter a busy observer which will be stopped
 | 
	
		
			
				|  |  | +                    //     in its tracks (as described above) or it will trigger a disposal of the subscription. In
 | 
	
		
			
				|  |  | +                    //     both these cases the result of awaiting DisposeAsync guarantees no further message flow.
 | 
	
		
			
				|  |  | +                    //
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    if (!_disposing)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        _disposing = true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                        task = _task;
 | 
	
		
			
				|  |  | +                        subscription = _subscription;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                try
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    if (task != null)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        await task.ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                finally
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    if (subscription != null)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        await subscription.DisposeAsync().ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            protected override async Task OnCompletedAsyncCore()
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                lock (_gate)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    if (_disposing)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        return;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    _task = _observer.OnCompletedAsync();
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                try
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    await _task.ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                finally
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    await FinishAsync().ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            protected override Task OnCompletedAsyncCore()
 | 
	
		
			
				|  |  | +            protected override async Task OnErrorAsyncCore(Exception error)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                throw new NotImplementedException();
 | 
	
		
			
				|  |  | +                lock (_gate)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    if (_disposing)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        return;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    _task = _observer.OnErrorAsync(error);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                try
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    await _task.ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                finally
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    await FinishAsync().ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            protected override Task OnErrorAsyncCore(Exception error)
 | 
	
		
			
				|  |  | +            protected override async Task OnNextAsyncCore(T value)
 | 
	
		
			
				|  |  |              {
 | 
	
		
			
				|  |  | -                throw new NotImplementedException();
 | 
	
		
			
				|  |  | +                lock (_gate)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    if (_disposing)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        return;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    _task = _observer.OnNextAsync(value);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                try
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    await _task.ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                finally
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    lock (_gate)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        _task = null;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -            protected override Task OnNextAsyncCore(T value) => _observer.OnNextAsync(value);
 | 
	
		
			
				|  |  | +            private async Task FinishAsync()
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                var subscription = default(IAsyncDisposable);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                lock (_gate)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    if (!_disposing)
 | 
	
		
			
				|  |  | +                    {
 | 
	
		
			
				|  |  | +                        _disposing = true;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                        subscription = _subscription;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                    _task = null;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if (subscription != null)
 | 
	
		
			
				|  |  | +                {
 | 
	
		
			
				|  |  | +                    await subscription.DisposeAsync().ConfigureAwait(false);
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 |