Skip to content

Commit 06fab49

Browse files
committed
Available streams count is thread safe
1 parent e99641d commit 06fab49

2 files changed

Lines changed: 38 additions & 12 deletions

File tree

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

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,11 @@ static async ValueTask<QuicConnection> StartConnectAsync(QuicClientConnectionOpt
175175
/// </summary>
176176
private IPEndPoint _localEndPoint = null!;
177177
/// <summary>
178+
/// Represents how many bidirectional streams can be accepted by the peer. Is only manipulated from MsQuic thread.
178179
/// </summary>
179180
private int _availableBidirectionalStreamsCount;
180181
/// <summary>
182+
/// Represents how many unidirectional streams can be accepted by the peer. Is only manipulated from MsQuic thread.
181183
/// </summary>
182184
private int _availableUnidirectionalStreamsCount;
183185
/// <summary>
@@ -434,6 +436,25 @@ internal ValueTask FinishHandshakeAsync(QuicServerConnectionOptions options, str
434436
return valueTask;
435437
}
436438

439+
/// <summary>
440+
/// In order to provide meaningful increments in <see cref="StreamsAvailable"/>, available streams count can be only manipulated from MsQuic thread.
441+
/// For that purpose we pass this function to <see cref="QuicStream"/> so that it can call it from <c>START_COMPLETE</c> event handler.
442+
///
443+
/// Note that MsQuic itself manipulates stream counts right before indicating <c>START_COMPLETE</c> event.
444+
/// </summary>
445+
/// <param name="streamType">Type of the stream to decrement appropriate field.</param>
446+
private void DecrementAvailableStreamCount(QuicStreamType streamType)
447+
{
448+
if (streamType == QuicStreamType.Unidirectional)
449+
{
450+
--_availableUnidirectionalStreamsCount;
451+
}
452+
else
453+
{
454+
--_availableBidirectionalStreamsCount;
455+
}
456+
}
457+
437458
/// <summary>
438459
/// Create an outbound uni/bidirectional <see cref="QuicStream" />.
439460
/// In case the connection doesn't have any available stream capacity, i.e.: the peer limits the concurrent stream count,
@@ -456,15 +477,7 @@ public async ValueTask<QuicStream> OpenOutboundStreamAsync(QuicStreamType type,
456477
NetEventSource.Info(this, $"{this} New outbound {type} stream {stream}.");
457478
}
458479

459-
await stream.StartAsync(cancellationToken).ConfigureAwait(false);
460-
if (type == QuicStreamType.Unidirectional)
461-
{
462-
Interlocked.Decrement(ref _availableUnidirectionalStreamsCount);
463-
}
464-
else
465-
{
466-
Interlocked.Decrement(ref _availableBidirectionalStreamsCount);
467-
}
480+
await stream.StartAsync(DecrementAvailableStreamCount, cancellationToken).ConfigureAwait(false);
468481
}
469482
catch
470483
{
@@ -637,8 +650,8 @@ private unsafe int HandleEventStreamsAvailable(ref STREAMS_AVAILABLE_DATA data)
637650
{
638651
int bidirectionalStreamsCountIncrement = data.BidirectionalCount - _availableBidirectionalStreamsCount;
639652
int unidirectionalStreamsCountIncrement = data.UnidirectionalCount - _availableUnidirectionalStreamsCount;
640-
Volatile.Write(ref _availableBidirectionalStreamsCount, data.BidirectionalCount);
641-
Volatile.Write(ref _availableUnidirectionalStreamsCount, data.UnidirectionalCount);
653+
_availableBidirectionalStreamsCount = data.BidirectionalCount;
654+
_availableUnidirectionalStreamsCount = data.UnidirectionalCount;
642655
OnStreamsAvailable(bidirectionalStreamsCountIncrement, unidirectionalStreamsCountIncrement);
643656
return QUIC_STATUS_SUCCESS;
644657
}

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ public sealed partial class QuicStream
120120
private long _id = -1;
121121
private readonly QuicStreamType _type;
122122

123+
/// <summary>
124+
/// Provided via <see cref="StartAsync(Action{QuicStreamType}, CancellationToken)" /> from <see cref="QuicConnection" /> so that <see cref="QuicStream"/> can decrement its available stream count field.
125+
/// When <see cref="HandleEventStartComplete(ref START_COMPLETE_DATA)">START_COMPLETE</see> arrives it gets invoked and unset back to <c>null</c> to not to hold any unintended reference to <see cref="QuicConnection"/>.
126+
/// </summary>
127+
private Action<QuicStreamType>? _decrementAvailableStreamCount;
128+
123129
/// <summary>
124130
/// Stream id, see <see href="https://www.rfc-editor.org/rfc/rfc9000.html#name-stream-types-and-identifier" />.
125131
/// </summary>
@@ -234,9 +240,10 @@ internal unsafe QuicStream(MsQuicContextSafeHandle connectionHandle, QUIC_HANDLE
234240
/// If no more concurrent streams can be opened at the moment, the operation will wait until it can,
235241
/// either by closing some existing streams or receiving more available stream ids from the peer.
236242
/// </summary>
243+
/// <param name="decrementAvailableStreamCount"></param>
237244
/// <param name="cancellationToken">A cancellation token that can be used to cancel the asynchronous operation.</param>
238245
/// <returns>An asynchronous task that completes with the opened <see cref="QuicStream" />.</returns>
239-
internal ValueTask StartAsync(CancellationToken cancellationToken = default)
246+
internal ValueTask StartAsync(Action<QuicStreamType> decrementAvailableStreamCount, CancellationToken cancellationToken = default)
240247
{
241248
_startedTcs.TryInitialize(out ValueTask valueTask, this, cancellationToken);
242249
{
@@ -252,6 +259,7 @@ internal ValueTask StartAsync(CancellationToken cancellationToken = default)
252259
}
253260
}
254261

262+
_decrementAvailableStreamCount = decrementAvailableStreamCount;
255263
return valueTask;
256264
}
257265

@@ -518,9 +526,13 @@ public void CompleteWrites()
518526

519527
private unsafe int HandleEventStartComplete(ref START_COMPLETE_DATA data)
520528
{
529+
Debug.Assert(_decrementAvailableStreamCount is not null);
530+
521531
_id = unchecked((long)data.ID);
522532
if (StatusSucceeded(data.Status))
523533
{
534+
_decrementAvailableStreamCount(Type);
535+
524536
if (data.PeerAccepted != 0)
525537
{
526538
_startedTcs.TrySetResult();
@@ -535,6 +547,7 @@ private unsafe int HandleEventStartComplete(ref START_COMPLETE_DATA data)
535547
}
536548
}
537549

550+
_decrementAvailableStreamCount = null;
538551
return QUIC_STATUS_SUCCESS;
539552
}
540553
private unsafe int HandleEventReceive(ref RECEIVE_DATA data)

0 commit comments

Comments
 (0)