Skip to content

Commit 6306816

Browse files
committed
Revert "Use ordinary TCS and revert changes in ValueTaskSource"
This reverts commit f393313f886bf2073ddaea9b02f9bb30b80f047b.
1 parent 3a4e393 commit 6306816

File tree

3 files changed

+65
-43
lines changed

3 files changed

+65
-43
lines changed

src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ValueTaskSource.cs

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@ private enum State : byte
2525
private State _state;
2626
private ManualResetValueTaskSourceCore<bool> _valueTaskSource;
2727
private CancellationTokenRegistration _cancellationRegistration;
28+
private Exception? _exception;
2829
private GCHandle _keepAlive;
2930

3031
public ValueTaskSource()
3132
{
3233
_state = State.None;
3334
_valueTaskSource = new ManualResetValueTaskSourceCore<bool>() { RunContinuationsAsynchronously = true };
3435
_cancellationRegistration = default;
36+
_exception = default;
3537
_keepAlive = default;
3638
}
3739

@@ -75,7 +77,7 @@ public bool TryInitialize(out ValueTask valueTask, object? keepAlive = null, Can
7577
State state = _state;
7678

7779
// If we're the first here, we will return true.
78-
if (state == State.None)
80+
if (state == State.None && _valueTaskSource.GetStatus(_valueTaskSource.Version) == ValueTaskSourceStatus.Pending)
7981
{
8082
// Keep alive the caller object until the result is read from the task.
8183
// Used for keeping caller alive during async interop calls.
@@ -104,30 +106,42 @@ private bool TryComplete(Exception? exception)
104106
{
105107
State state = _state;
106108

107-
if (state != State.Completed)
109+
// Completed: nothing to do.
110+
if (state == State.Completed)
108111
{
109-
_state = State.Completed;
112+
return false;
113+
}
114+
115+
// With cancellation, keep the state as-is so it can be restored after the OCE is consumed.
116+
_state = exception is OperationCanceledException ? state : State.Completed;
110117

111-
// Swap the cancellation registration so the one that's been registered gets eventually Disposed.
112-
// Ideally, we would dispose it here, but if the callbacks kicks in, it tries to take the lock held by this thread leading to deadlock.
113-
cancellationRegistration = _cancellationRegistration;
114-
_cancellationRegistration = default;
118+
// Swap the cancellation registration so the one that's been registered gets eventually Disposed.
119+
// Ideally, we would dispose it here, but if the callbacks kicks in, it tries to take the lock held by this thread leading to deadlock.
120+
cancellationRegistration = _cancellationRegistration;
121+
_cancellationRegistration = default;
115122

116-
if (exception is not null)
123+
if (exception is not null)
124+
{
125+
// Set up the exception stack trace for the caller.
126+
exception = exception.StackTrace is null ? ExceptionDispatchInfo.SetCurrentStackTrace(exception) : exception;
127+
if (_valueTaskSource.GetStatus(_valueTaskSource.Version) == ValueTaskSourceStatus.Pending)
117128
{
118-
// Set up the exception stack trace for the caller.
119-
exception = exception.StackTrace is null ? ExceptionDispatchInfo.SetCurrentStackTrace(exception) : exception;
120129
_valueTaskSource.SetException(exception);
121130
}
122-
else
131+
else if (exception is not OperationCanceledException)
132+
{
133+
_exception = exception;
134+
}
135+
}
136+
else
137+
{
138+
if (_valueTaskSource.GetStatus(_valueTaskSource.Version) == ValueTaskSourceStatus.Pending)
123139
{
124140
_valueTaskSource.SetResult(true);
125141
}
126-
127-
return true;
128142
}
129143

130-
return false;
144+
return true;
131145
}
132146
finally
133147
{
@@ -173,5 +187,34 @@ void IValueTaskSource.OnCompleted(Action<object?> continuation, object? state, s
173187
=> _valueTaskSource.OnCompleted(continuation, state, token, flags);
174188

175189
void IValueTaskSource.GetResult(short token)
176-
=> _valueTaskSource.GetResult(token);
190+
{
191+
try
192+
{
193+
_valueTaskSource.GetResult(token);
194+
}
195+
finally
196+
{
197+
lock (this)
198+
{
199+
State state = _state;
200+
201+
// In case of a cancellation, reset the task and set the stored results if necessary.
202+
if (_valueTaskSource.GetStatus(_valueTaskSource.Version) == ValueTaskSourceStatus.Canceled)
203+
{
204+
_valueTaskSource.Reset();
205+
if (state == State.Completed)
206+
{
207+
if (_exception is not null)
208+
{
209+
_valueTaskSource.SetException(_exception);
210+
}
211+
else
212+
{
213+
_valueTaskSource.SetResult(true);
214+
}
215+
}
216+
}
217+
}
218+
}
219+
}
177220
}

src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ static async ValueTask<QuicConnection> StartConnectAsync(QuicClientConnectionOpt
109109
private int _disposed;
110110

111111
private readonly ValueTaskSource _connectedTcs = new ValueTaskSource();
112-
private TaskCompletionSource? _shutdownTcs;
112+
private readonly ValueTaskSource _shutdownTcs = new ValueTaskSource();
113113

114114
private readonly CancellationTokenSource _shutdownTokenSource = new CancellationTokenSource();
115115

@@ -467,7 +467,7 @@ public ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken
467467
{
468468
ObjectDisposedException.ThrowIf(_disposed == 1, this);
469469

470-
if (Interlocked.CompareExchange(ref _shutdownTcs, new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously), null) == null)
470+
if (_shutdownTcs.TryInitialize(out ValueTask valueTask, this, cancellationToken))
471471
{
472472
unsafe
473473
{
@@ -478,7 +478,7 @@ public ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken
478478
}
479479
}
480480

481-
return new ValueTask(_shutdownTcs.Task.WaitAsync(cancellationToken));
481+
return valueTask;
482482
}
483483

484484
private unsafe int HandleEventConnected(ref CONNECTED_DATA data)
@@ -520,7 +520,6 @@ private unsafe int HandleEventShutdownComplete()
520520
_acceptQueue.Writer.TryComplete(exception);
521521
_connectedTcs.TrySetException(exception);
522522
_shutdownTokenSource.Cancel();
523-
Interlocked.CompareExchange(ref _shutdownTcs, new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously), null);
524523
_shutdownTcs.TrySetResult();
525524
return QUIC_STATUS_SUCCESS;
526525
}
@@ -627,7 +626,7 @@ public async ValueTask DisposeAsync()
627626
}
628627

629628
// Check if the connection has been shut down and if not, shut it down.
630-
if (Interlocked.CompareExchange(ref _shutdownTcs, new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously), null) == null)
629+
if (_shutdownTcs.TryInitialize(out ValueTask valueTask, this))
631630
{
632631
unsafe
633632
{
@@ -637,7 +636,7 @@ public async ValueTask DisposeAsync()
637636
(ulong)_defaultCloseErrorCode);
638637
}
639638
}
640-
else if (!_shutdownTcs.Task.IsCompletedSuccessfully)
639+
else if (!valueTask.IsCompletedSuccessfully)
641640
{
642641
unsafe
643642
{
@@ -649,7 +648,7 @@ public async ValueTask DisposeAsync()
649648
}
650649

651650
// Wait for SHUTDOWN_COMPLETE, the last event, so that all resources can be safely released.
652-
await _shutdownTcs.Task.ConfigureAwait(false);
651+
await valueTask.ConfigureAwait(false);
653652
Debug.Assert(_connectedTcs.IsCompleted);
654653
_handle.Dispose();
655654
_shutdownTokenSource.Dispose();

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -134,27 +134,7 @@ await RunClientServer(
134134
{
135135
var cts = new CancellationTokenSource();
136136
cts.Cancel();
137-
await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await clientConnection.CloseAsync(ExpectedErrorCode, cts.Token));
138-
await clientConnection.DisposeAsync();
139-
sync.Release();
140-
},
141-
async serverConnection =>
142-
{
143-
await sync.WaitAsync();
144-
await serverConnection.DisposeAsync();
145-
});
146-
}
147-
148-
[Fact]
149-
public async Task DisposeAfterCloseTaskStored()
150-
{
151-
using var sync = new SemaphoreSlim(0);
152-
153-
await RunClientServer(
154-
async clientConnection =>
155-
{
156-
var cts = new CancellationTokenSource();
157-
var task = clientConnection.CloseAsync(0).AsTask();
137+
await Assert.ThrowsAsync<OperationCanceledException>(async () => await clientConnection.CloseAsync(ExpectedErrorCode, cts.Token));
158138
await clientConnection.DisposeAsync();
159139
sync.Release();
160140
},

0 commit comments

Comments
 (0)