Skip to content

Commit 8e407e3

Browse files
authored
[QUIC] Copy managed memory instead of pinning (#72746)
* Improved server logging to include S.N.Quic traces as well. * MsQuicBuffers copy given memory into native instead of pinning
1 parent d0aa175 commit 8e407e3

File tree

4 files changed

+73
-28
lines changed

4 files changed

+73
-28
lines changed

src/libraries/System.Net.Http/tests/StressTests/HttpStress/ClientOperations.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, Htt
8585
await Task.WhenAny(task, Task.Delay(_random.Next(0, 60), cts.Token));
8686
}
8787

88-
cts.Cancel();
8988
IsCancellationRequested = true;
89+
cts.Cancel();
9090
return WithVersionValidation(await task);
9191
}
9292
else

src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
using Microsoft.Extensions.Primitives;
2828
using Microsoft.AspNetCore.Server.Kestrel.Core;
2929
using Serilog;
30+
using Microsoft.Extensions.ObjectPool;
3031

3132
namespace HttpStress
3233
{
@@ -36,6 +37,7 @@ public class StressServer : IDisposable
3637
public const string ExpectedResponseContentLength = "Expected-Response-Content-Length";
3738

3839
private readonly IWebHost _webHost;
40+
private readonly LogQuicEventListener? _listener;
3941

4042
public string ServerUri { get; }
4143

@@ -155,6 +157,10 @@ void ConfigureListenOptions(ListenOptions listenOptions)
155157
.WriteTo.Console(Serilog.Events.LogEventLevel.Warning);
156158
}
157159
Log.Logger = loggerConfiguration.CreateLogger();
160+
if (configuration.Trace)
161+
{
162+
_listener = new LogQuicEventListener(Log.Logger);
163+
}
158164

159165
host = host
160166
.UseSerilog()
@@ -333,6 +339,7 @@ private static void AppendChecksumHeader(IHeaderDictionary headers, ulong checks
333339
public void Dispose()
334340
{
335341
_webHost.Dispose();
342+
_listener?.Dispose();
336343
}
337344

338345
private static (string scheme, string hostname, int port) ParseServerUri(string serverUri)
@@ -397,4 +404,51 @@ public static bool IsValidServerContent(string input)
397404
return true;
398405
}
399406
}
407+
408+
public class LogQuicEventListener : EventListener
409+
{
410+
private DefaultObjectPool<StringBuilder> _stringBuilderPool = new DefaultObjectPool<StringBuilder>(new StringBuilderPooledObjectPolicy());
411+
private readonly Serilog.ILogger _logger;
412+
413+
public LogQuicEventListener(Serilog.ILogger logger)
414+
{
415+
_logger = logger;
416+
}
417+
418+
protected override void OnEventSourceCreated(EventSource eventSource)
419+
{
420+
if (eventSource.Name == "Private.InternalDiagnostics.System.Net.Quic")
421+
{
422+
EnableEvents(eventSource, EventLevel.LogAlways);
423+
}
424+
}
425+
426+
protected override void OnEventWritten(EventWrittenEventArgs eventData)
427+
{
428+
StringBuilder sb = _stringBuilderPool.Get();
429+
sb.Append($"{eventData.TimeStamp:HH:mm:ss.fffffff}[{eventData.EventName}] ");
430+
for (int i = 0; i < eventData.Payload?.Count; i++)
431+
{
432+
if (i > 0)
433+
{
434+
sb.Append(", ");
435+
}
436+
sb.Append(eventData.PayloadNames?[i]).Append(": ").Append(eventData.Payload[i]);
437+
}
438+
if (eventData.Level > EventLevel.Error)
439+
{
440+
_logger.Debug(sb.ToString());
441+
}
442+
else
443+
{
444+
_logger.Error(sb.ToString());
445+
}
446+
_stringBuilderPool.Return(sb);
447+
}
448+
449+
public override void Dispose()
450+
{
451+
base.Dispose();
452+
}
453+
}
400454
}

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

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Buffers;
54
using System.Collections.Generic;
65
using System.Runtime.InteropServices;
76
using Microsoft.Quic;
@@ -15,16 +14,13 @@ namespace System.Net.Quic;
1514
/// </summary>
1615
internal unsafe struct MsQuicBuffers : IDisposable
1716
{
18-
// Handles to pinned memory blocks from the user.
19-
private MemoryHandle[] _handles;
2017
// Native memory block which holds the pinned memory pointers from _handles and can be passed to MsQuic as QUIC_BUFFER*.
2118
private QUIC_BUFFER* _buffers;
2219
// Number of QUIC_BUFFER instance currently allocated in _buffers, so that we can reuse the memory instead of reallocating.
2320
private int _count;
2421

2522
public MsQuicBuffers()
2623
{
27-
_handles = Array.Empty<MemoryHandle>();
2824
_buffers = null;
2925
_count = 0;
3026
}
@@ -37,44 +33,39 @@ private void FreeNativeMemory()
3733
QUIC_BUFFER* buffers = _buffers;
3834
_buffers = null;
3935
NativeMemory.Free(buffers);
36+
_count = 0;
4037
}
4138

4239
private void Reserve(int count)
4340
{
44-
if (_handles.Length < count)
41+
if (count > _count)
4542
{
46-
_handles = new MemoryHandle[count];
4743
FreeNativeMemory();
48-
_buffers = (QUIC_BUFFER*)NativeMemory.Alloc((nuint)count, (nuint)sizeof(QUIC_BUFFER));
44+
_buffers = (QUIC_BUFFER*)NativeMemory.AllocZeroed((nuint)count, (nuint)sizeof(QUIC_BUFFER));
45+
_count = count;
4946
}
50-
51-
_count = count;
5247
}
5348

5449
private void SetBuffer(int index, ReadOnlyMemory<byte> buffer)
5550
{
56-
MemoryHandle handle = buffer.Pin();
57-
58-
_handles[index] = handle;
59-
_buffers[index].Buffer = (byte*)handle.Pointer;
51+
_buffers[index].Buffer = (byte*)NativeMemory.Alloc((nuint)buffer.Length, (nuint)sizeof(byte));
6052
_buffers[index].Length = (uint)buffer.Length;
53+
buffer.Span.CopyTo(_buffers[index].Span);
6154
}
6255

6356
/// <summary>
6457
/// Initializes QUIC_BUFFER* with data from inputs, converted via toBuffer.
6558
/// Note that the struct either needs to be freshly created via new or previously cleaned up with Reset.
6659
/// </summary>
67-
/// <param name="inputs">Inputs to get their byte array, pin them and pepare them to be passed to MsQuic as QUIC_BUFFER*.</param>
60+
/// <param name="inputs">Inputs to get their byte array, copy them to be passed to MsQuic as QUIC_BUFFER*.</param>
6861
/// <param name="toBuffer">Method extracting byte array from the inputs, e.g. applicationProtocol.Protocol.</param>
6962
/// <typeparam name="T">The type of the inputs.</typeparam>
7063
public void Initialize<T>(IList<T> inputs, Func<T, ReadOnlyMemory<byte>> toBuffer)
7164
{
7265
Reserve(inputs.Count);
73-
7466
for (int i = 0; i < inputs.Count; ++i)
7567
{
76-
ReadOnlyMemory<byte> buffer = toBuffer(inputs[i]);
77-
SetBuffer(i, buffer);
68+
SetBuffer(i, toBuffer(inputs[i]));
7869
}
7970
}
8071

@@ -90,19 +81,25 @@ public void Initialize(ReadOnlyMemory<byte> buffer)
9081
}
9182

9283
/// <summary>
93-
/// Unpins the managed memory and allows reuse of this struct.
84+
/// Release the native memory of individual buffers and allows reuse of this struct.
9485
/// </summary>
9586
public void Reset()
9687
{
9788
for (int i = 0; i < _count; ++i)
9889
{
99-
_handles[i].Dispose();
90+
if (_buffers[i].Buffer is null)
91+
{
92+
break;
93+
}
94+
byte* buffer = _buffers[i].Buffer;
95+
_buffers[i].Buffer = null;
96+
NativeMemory.Free(buffer);
97+
_buffers[i].Length = 0;
10098
}
10199
}
102100

103101
/// <summary>
104-
/// Apart from unpinning the managed memory, it returns the shared buffer,
105-
/// but most importantly it releases the unmanaged memory.
102+
/// Releases all the native memory.
106103
/// </summary>
107104
public void Dispose()
108105
{

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,7 @@ public sealed partial class QuicStream
7676
}
7777
}
7878
};
79-
// [ActiveIssue("https://github.com/dotnet/roslyn-analyzers/issues/5750")] Structs can have parameterless ctor now and thus the behavior differs from just defaulting the struct to zeros.
80-
#pragma warning disable CA1805
8179
private ReceiveBuffers _receiveBuffers = new ReceiveBuffers();
82-
#pragma warning restore CA1805
8380
private int _receivedNeedsEnable;
8481

8582
private readonly ResettableValueTaskSource _sendTcs = new ResettableValueTaskSource()
@@ -92,10 +89,7 @@ public sealed partial class QuicStream
9289
}
9390
}
9491
};
95-
// [ActiveIssue("https://github.com/dotnet/roslyn-analyzers/issues/5750")] Structs can have parameterless ctor now and thus the behavior differs from just defaulting the struct to zeros.
96-
#pragma warning disable CA1805
9792
private MsQuicBuffers _sendBuffers = new MsQuicBuffers();
98-
#pragma warning restore CA1805
9993

10094
private readonly long _defaultErrorCode;
10195

0 commit comments

Comments
 (0)