Skip to content

Commit bf1e941

Browse files
committed
Capture ExceptionDispatchInfo in ToAsyncEnumerable
1 parent 2852824 commit bf1e941

File tree

3 files changed

+51
-13
lines changed

3 files changed

+51
-13
lines changed

Ix.NET/Source/System.Linq.Async.Tests/System/Linq/AsyncEnumerableTests.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,16 @@ public class AsyncEnumerableTests
1515
{
1616
protected static readonly IAsyncEnumerable<int> Return42 = new[] { 42 }.ToAsyncEnumerable();
1717

18-
protected async Task AssertThrowsAsync<TException>(Task t)
18+
protected async Task<TException> AssertThrowsAsync<TException>(Task t)
1919
where TException : Exception
2020
{
21-
await Assert.ThrowsAsync<TException>(() => t);
21+
return await Assert.ThrowsAsync<TException>(() => t);
2222
}
2323

2424
protected async Task AssertThrowsAsync(Task t, Exception e)
2525
{
26-
try
27-
{
28-
await t;
29-
}
30-
catch (Exception ex)
31-
{
32-
Assert.Same(e, ex);
33-
}
26+
var ex = await Assert.ThrowsAnyAsync<Exception>(() => t);
27+
Assert.Same(e, ex);
3428
}
3529

3630
protected Task AssertThrowsAsync<T>(ValueTask<T> t, Exception e)

Ix.NET/Source/System.Linq.Async.Tests/System/Linq/Operators/ToAsyncEnumerable.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Linq;
8+
using System.Runtime.CompilerServices;
89
using System.Threading;
910
using System.Threading.Tasks;
1011
using Xunit;
@@ -283,6 +284,48 @@ public async Task ToAsyncEnumerable_Observable_Throw()
283284
await AssertThrowsAsync(e.MoveNextAsync(), ex);
284285
}
285286

287+
[Fact]
288+
public async Task ToAsyncEnumerable_Observable_Throw_ActiveException()
289+
{
290+
// No inlining as we're asserting that this function must be mentioned in the exception stacktrace
291+
[MethodImpl(MethodImplOptions.NoInlining)]
292+
void ThrowsException()
293+
{
294+
throw new Exception("Bang!");
295+
}
296+
297+
var subscribed = false;
298+
299+
var xs = new MyObservable<int>(obs =>
300+
{
301+
subscribed = true;
302+
303+
try
304+
{
305+
ThrowsException();
306+
}
307+
catch (Exception ex)
308+
{
309+
// `ex` is active at this point as it has been thrown.
310+
// Therefore the stack trace should be captured by ToAsyncEnumerable.
311+
// See 'Remarks' at https://docs.microsoft.com/en-us/dotnet/api/system.runtime.exceptionservices.exceptiondispatchinfo.capture
312+
obs.OnError(ex);
313+
}
314+
315+
return new MyDisposable(() => { });
316+
}).ToAsyncEnumerable();
317+
318+
Assert.False(subscribed);
319+
320+
var e = xs.GetAsyncEnumerator();
321+
322+
// NB: Breaking change to align with lazy nature of async iterators.
323+
// Assert.True(subscribed);
324+
325+
var actual = await AssertThrowsAsync<Exception>(e.MoveNextAsync().AsTask());
326+
Assert.Contains(nameof(ThrowsException), actual.StackTrace);
327+
}
328+
286329
[Fact]
287330
public async Task ToAsyncEnumerable_Observable_Dispose()
288331
{

Ix.NET/Source/System.Linq.Async/System/Linq/Operators/ToAsyncEnumerable.Observable.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System.Collections.Concurrent;
66
using System.Collections.Generic;
7+
using System.Runtime.ExceptionServices;
78
using System.Threading;
89
using System.Threading.Tasks;
910

@@ -31,7 +32,7 @@ private sealed class ObservableAsyncEnumerable<TSource> : AsyncIterator<TSource>
3132
private readonly IObservable<TSource> _source;
3233

3334
private ConcurrentQueue<TSource>? _values = new ConcurrentQueue<TSource>();
34-
private Exception? _error;
35+
private ExceptionDispatchInfo? _error;
3536
private bool _completed;
3637
private TaskCompletionSource<bool>? _signal;
3738
private IDisposable? _subscription;
@@ -95,7 +96,7 @@ protected override async ValueTask<bool> MoveNextCore()
9596

9697
if (error != null)
9798
{
98-
throw error;
99+
error.Throw();
99100
}
100101

101102
return false;
@@ -120,7 +121,7 @@ public void OnCompleted()
120121

121122
public void OnError(Exception error)
122123
{
123-
_error = error;
124+
_error = ExceptionDispatchInfo.Capture(error);
124125
Volatile.Write(ref _completed, true);
125126

126127
DisposeSubscription();

0 commit comments

Comments
 (0)