Browse Source

Update aspnetcore Platform to use latest Razor Slices (#9464)

* Update aspnetcore Platform to use latest Razor Slices

This allows enabling native AOT too

* Update README.md

* Update ChunkedPipeWriter.cs

* Update Npgsql & seal classes

* Update ChunkedPipeWriter.cs
Damian Edwards 7 months ago
parent
commit
491de7311f

+ 1 - 1
frameworks/CSharp/aspnetcore/README.md

@@ -6,5 +6,5 @@ See [.NET Core](http://dot.net) and [ASP.NET Core](https://github.com/dotnet/asp
 
 
 **Language**
 **Language**
 
 
-* C# 8.0
+* C# 13.0
 
 

+ 1 - 0
frameworks/CSharp/aspnetcore/benchmark_config.json

@@ -30,6 +30,7 @@
         "json_url": "/json",
         "json_url": "/json",
         "db_url": "/db",
         "db_url": "/db",
         "query_url": "/queries/",
         "query_url": "/queries/",
+        "fortune_url": "/fortunes",
         "update_url": "/updates/",
         "update_url": "/updates/",
         "cached_query_url": "/cached-worlds/",
         "cached_query_url": "/cached-worlds/",
         "port": 8080,
         "port": 8080,

+ 1 - 1
frameworks/CSharp/aspnetcore/src/Minimal/Minimal.csproj

@@ -9,7 +9,7 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Npgsql" Version="8.0.5" />
+    <PackageReference Include="Npgsql" Version="9.0.2" />
     <PackageReference Include="Dapper" Version="2.1.35" />
     <PackageReference Include="Dapper" Version="2.1.35" />
     <PackageReference Include="RazorSlices" Version="0.8.1" />
     <PackageReference Include="RazorSlices" Version="0.8.1" />
   </ItemGroup>
   </ItemGroup>

+ 2 - 2
frameworks/CSharp/aspnetcore/src/Mvc/Mvc.csproj

@@ -9,8 +9,8 @@
   </PropertyGroup>
   </PropertyGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Npgsql" Version="8.0.5" />
-    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0-rc.2" />
+    <PackageReference Include="Npgsql" Version="9.0.2" />
+    <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
   </ItemGroup>
   </ItemGroup>
 
 
 </Project>
 </Project>

+ 1 - 1
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Caching.cs

@@ -6,7 +6,7 @@ using System.Threading.Tasks;
 
 
 namespace PlatformBenchmarks;
 namespace PlatformBenchmarks;
 
 
-public partial class BenchmarkApplication
+public sealed partial class BenchmarkApplication
 {
 {
     private static async Task Caching(PipeWriter pipeWriter, int count)
     private static async Task Caching(PipeWriter pipeWriter, int count)
     {
     {

+ 7 - 8
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Fortunes.cs

@@ -1,7 +1,7 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
 
 
-#if !AOT
+using System;
 using System.IO.Pipelines;
 using System.IO.Pipelines;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -9,7 +9,7 @@ using RazorSlices;
 
 
 namespace PlatformBenchmarks;
 namespace PlatformBenchmarks;
 
 
-public partial class BenchmarkApplication
+public sealed partial class BenchmarkApplication
 {
 {
     private async Task FortunesRaw(PipeWriter pipeWriter)
     private async Task FortunesRaw(PipeWriter pipeWriter)
     {
     {
@@ -19,7 +19,7 @@ public partial class BenchmarkApplication
             FortunesTemplateFactory);
             FortunesTemplateFactory);
     }
     }
 
 
-    private ValueTask OutputFortunes<TModel>(PipeWriter pipeWriter, TModel model, SliceFactory<TModel> templateFactory)
+    private ValueTask OutputFortunes<TModel>(PipeWriter pipeWriter, TModel model, Func<TModel, RazorSlice<TModel>> templateFactory)
     {
     {
         // Render headers
         // Render headers
         var preamble = """
         var preamble = """
@@ -39,7 +39,7 @@ public partial class BenchmarkApplication
         // Kestrel PipeWriter span size is 4K, headers above already written to first span & template output is ~1350 bytes,
         // Kestrel PipeWriter span size is 4K, headers above already written to first span & template output is ~1350 bytes,
         // so 2K chunk size should result in only a single span and chunk being used.
         // so 2K chunk size should result in only a single span and chunk being used.
         var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSizeHint: 2048);
         var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSizeHint: 2048);
-        var renderTask = template.RenderAsync(chunkedWriter, null, HtmlEncoder);
+        var renderTask = template.RenderAsync(chunkedWriter, HtmlEncoder);
 
 
         if (renderTask.IsCompletedSuccessfully)
         if (renderTask.IsCompletedSuccessfully)
         {
         {
@@ -51,18 +51,17 @@ public partial class BenchmarkApplication
         return AwaitTemplateRenderTask(renderTask, chunkedWriter, template);
         return AwaitTemplateRenderTask(renderTask, chunkedWriter, template);
     }
     }
 
 
-    private static async ValueTask AwaitTemplateRenderTask(ValueTask renderTask, ChunkedBufferWriter<WriterAdapter> chunkedWriter, RazorSlice template)
+    private static async ValueTask AwaitTemplateRenderTask(ValueTask renderTask, ChunkedPipeWriter chunkedWriter, RazorSlice template)
     {
     {
         await renderTask;
         await renderTask;
         EndTemplateRendering(chunkedWriter, template);
         EndTemplateRendering(chunkedWriter, template);
     }
     }
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private static void EndTemplateRendering(ChunkedBufferWriter<WriterAdapter> chunkedWriter, RazorSlice template)
+    private static void EndTemplateRendering(ChunkedPipeWriter chunkedWriter, RazorSlice template)
     {
     {
-        chunkedWriter.End();
+        chunkedWriter.Complete();
         ReturnChunkedWriter(chunkedWriter);
         ReturnChunkedWriter(chunkedWriter);
         template.Dispose();
         template.Dispose();
     }
     }
 }
 }
-#endif

+ 4 - 4
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.HttpConnection.cs

@@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
 
 
 namespace PlatformBenchmarks;
 namespace PlatformBenchmarks;
 
 
-public partial class BenchmarkApplication : IHttpConnection
+public sealed partial class BenchmarkApplication : IHttpConnection
 {
 {
     private State _state;
     private State _state;
 
 
@@ -193,15 +193,15 @@ public partial class BenchmarkApplication : IHttpConnection
         => new(new(pipeWriter), sizeHint);
         => new(new(pipeWriter), sizeHint);
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private static ChunkedBufferWriter<WriterAdapter> GetChunkedWriter(PipeWriter pipeWriter, int chunkSizeHint)
+    private static ChunkedPipeWriter GetChunkedWriter(PipeWriter pipeWriter, int chunkSizeHint)
     {
     {
         var writer = ChunkedWriterPool.Get();
         var writer = ChunkedWriterPool.Get();
-        writer.SetOutput(new WriterAdapter(pipeWriter), chunkSizeHint);
+        writer.SetOutput(pipeWriter, chunkSizeHint);
         return writer;
         return writer;
     }
     }
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    private static void ReturnChunkedWriter(ChunkedBufferWriter<WriterAdapter> writer) => ChunkedWriterPool.Return(writer);
+    private static void ReturnChunkedWriter(ChunkedPipeWriter writer) => ChunkedWriterPool.Return(writer);
 
 
     private struct WriterAdapter : IBufferWriter<byte>
     private struct WriterAdapter : IBufferWriter<byte>
     {
     {

+ 1 - 1
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Json.cs

@@ -9,7 +9,7 @@ using System.Threading.Tasks;
 
 
 namespace PlatformBenchmarks;
 namespace PlatformBenchmarks;
 
 
-public partial class BenchmarkApplication
+public sealed partial class BenchmarkApplication
 {
 {
     private readonly static uint _jsonPayloadSize = (uint)JsonSerializer.SerializeToUtf8Bytes(
     private readonly static uint _jsonPayloadSize = (uint)JsonSerializer.SerializeToUtf8Bytes(
         new JsonMessage { message = "Hello, World!" },
         new JsonMessage { message = "Hello, World!" },

+ 1 - 1
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.MultipleQueries.cs

@@ -8,7 +8,7 @@ using System.Text.Json.Serialization.Metadata;
 
 
 namespace PlatformBenchmarks;
 namespace PlatformBenchmarks;
 
 
-public partial class BenchmarkApplication
+public sealed partial class BenchmarkApplication
 {
 {
     private static async Task MultipleQueries(PipeWriter pipeWriter, int count)
     private static async Task MultipleQueries(PipeWriter pipeWriter, int count)
     {
     {

+ 1 - 1
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Plaintext.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 
 
 namespace PlatformBenchmarks;
 namespace PlatformBenchmarks;
 
 
-public partial class BenchmarkApplication
+public sealed partial class BenchmarkApplication
 {
 {
     private static ReadOnlySpan<byte> _plaintextPreamble =>
     private static ReadOnlySpan<byte> _plaintextPreamble =>
         "HTTP/1.1 200 OK\r\n"u8 +
         "HTTP/1.1 200 OK\r\n"u8 +

+ 1 - 1
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.SingleQuery.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 
 
 namespace PlatformBenchmarks;
 namespace PlatformBenchmarks;
 
 
-public partial class BenchmarkApplication
+public sealed partial class BenchmarkApplication
 {
 {
     private static async Task SingleQuery(PipeWriter pipeWriter)
     private static async Task SingleQuery(PipeWriter pipeWriter)
     {
     {

+ 1 - 1
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.Updates.cs

@@ -7,7 +7,7 @@ using System.Threading.Tasks;
 
 
 namespace PlatformBenchmarks;
 namespace PlatformBenchmarks;
 
 
-public partial class BenchmarkApplication
+public sealed partial class BenchmarkApplication
 {
 {
     private static async Task Updates(PipeWriter pipeWriter, int count)
     private static async Task Updates(PipeWriter pipeWriter, int count)
     {
     {

+ 7 - 12
frameworks/CSharp/aspnetcore/src/Platform/BenchmarkApplication.cs

@@ -10,9 +10,8 @@ using System.Text.Json.Serialization;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
 using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
 using Microsoft.Extensions.ObjectPool;
 using Microsoft.Extensions.ObjectPool;
-#if !AOT
+using Platform.Templates;
 using RazorSlices;
 using RazorSlices;
-#endif
 
 
 namespace PlatformBenchmarks
 namespace PlatformBenchmarks
 {
 {
@@ -42,26 +41,24 @@ namespace PlatformBenchmarks
 
 
         public static RawDb RawDb { get; set; }
         public static RawDb RawDb { get; set; }
 
 
-        private static readonly DefaultObjectPool<ChunkedBufferWriter<WriterAdapter>> ChunkedWriterPool
+        private static readonly DefaultObjectPool<ChunkedPipeWriter> ChunkedWriterPool
             = new(new ChunkedWriterObjectPolicy());
             = new(new ChunkedWriterObjectPolicy());
 
 
-        private sealed class ChunkedWriterObjectPolicy : IPooledObjectPolicy<ChunkedBufferWriter<WriterAdapter>>
+        private sealed class ChunkedWriterObjectPolicy : IPooledObjectPolicy<ChunkedPipeWriter>
         {
         {
-            public ChunkedBufferWriter<WriterAdapter> Create() => new();
+            public ChunkedPipeWriter Create() => new();
 
 
-            public bool Return(ChunkedBufferWriter<WriterAdapter> writer)
+            public bool Return(ChunkedPipeWriter writer)
             {
             {
                 writer.Reset();
                 writer.Reset();
                 return true;
                 return true;
             }
             }
         }
         }
 
 
-#if !AOT
 #if NPGSQL
 #if NPGSQL
-        private readonly static SliceFactory<List<FortuneUtf8>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf8>>("/Templates/FortunesUtf8.cshtml");
+        private readonly static Func<List<FortuneUtf8>, RazorSlice<List<FortuneUtf8>>> FortunesTemplateFactory = FortunesUtf8.Create;
 #else
 #else
-        private readonly static SliceFactory<List<FortuneUtf16>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf16>>("/Templates/FortunesUtf16.cshtml");
-#endif
+        private readonly static Func<List<FortuneUtf16>, RazorSlice<List<FortuneUtf16>>> FortunesTemplateFactory = FortunesUtf16.Create;
 #endif
 #endif
 
 
         [ThreadStatic]
         [ThreadStatic]
@@ -167,9 +164,7 @@ namespace PlatformBenchmarks
 
 
         private Task ProcessRequestAsync() => _requestType switch
         private Task ProcessRequestAsync() => _requestType switch
         {
         {
-#if !AOT
             RequestType.FortunesRaw => FortunesRaw(Writer),
             RequestType.FortunesRaw => FortunesRaw(Writer),
-#endif
             RequestType.SingleQuery => SingleQuery(Writer),
             RequestType.SingleQuery => SingleQuery(Writer),
             RequestType.Caching => Caching(Writer, _queries),
             RequestType.Caching => Caching(Writer, _queries),
             RequestType.Updates => Updates(Writer, _queries),
             RequestType.Updates => Updates(Writer, _queries),

+ 35 - 8
frameworks/CSharp/aspnetcore/src/Platform/ChunkedBufferWriter.cs → frameworks/CSharp/aspnetcore/src/Platform/ChunkedPipeWriter.cs

@@ -5,35 +5,40 @@ using System;
 using System.Buffers;
 using System.Buffers;
 using System.Buffers.Text;
 using System.Buffers.Text;
 using System.Diagnostics;
 using System.Diagnostics;
+using System.IO.Pipelines;
 using System.Numerics;
 using System.Numerics;
 using System.Runtime.CompilerServices;
 using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
 
 
 namespace PlatformBenchmarks;
 namespace PlatformBenchmarks;
 
 
-internal sealed class ChunkedBufferWriter<TWriter> : IBufferWriter<byte> where TWriter : IBufferWriter<byte>
+internal sealed class ChunkedPipeWriter : PipeWriter
 {
 {
     private const int DefaultChunkSizeHint = 2048;
     private const int DefaultChunkSizeHint = 2048;
     private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint);
     private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint);
     private static ReadOnlySpan<byte> ChunkTerminator => "\r\n"u8;
     private static ReadOnlySpan<byte> ChunkTerminator => "\r\n"u8;
 
 
-    private TWriter _output;
+    private PipeWriter _output;
     private int _chunkSizeHint;
     private int _chunkSizeHint;
     private StandardFormat _hexFormat = DefaultHexFormat;
     private StandardFormat _hexFormat = DefaultHexFormat;
     private Memory<byte> _currentFullChunk;
     private Memory<byte> _currentFullChunk;
     private Memory<byte> _currentChunk;
     private Memory<byte> _currentChunk;
     private int _buffered;
     private int _buffered;
+    private long _unflushedBytes;
     private bool _ended = false;
     private bool _ended = false;
 
 
     public Memory<byte> Memory => _currentChunk;
     public Memory<byte> Memory => _currentChunk;
 
 
-    public TWriter Output => _output;
+    public PipeWriter Output => _output;
 
 
     public int Buffered => _buffered;
     public int Buffered => _buffered;
 
 
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void SetOutput(TWriter output, int chunkSizeHint = DefaultChunkSizeHint)
+    public void SetOutput(PipeWriter output, int chunkSizeHint = DefaultChunkSizeHint)
     {
     {
         _buffered = 0;
         _buffered = 0;
+        _unflushedBytes = 0;
         _chunkSizeHint = chunkSizeHint;
         _chunkSizeHint = chunkSizeHint;
         _output = output;
         _output = output;
 
 
@@ -44,6 +49,7 @@ internal sealed class ChunkedBufferWriter<TWriter> : IBufferWriter<byte> where T
     public void Reset()
     public void Reset()
     {
     {
         _buffered = 0;
         _buffered = 0;
+        _unflushedBytes = 0;
         _output = default;
         _output = default;
         _ended = false;
         _ended = false;
         _hexFormat = DefaultHexFormat;
         _hexFormat = DefaultHexFormat;
@@ -51,16 +57,21 @@ internal sealed class ChunkedBufferWriter<TWriter> : IBufferWriter<byte> where T
         _currentChunk = default;
         _currentChunk = default;
     }
     }
 
 
+    public override bool CanGetUnflushedBytes => true;
+
+    public override long UnflushedBytes => _unflushedBytes;
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
-    public void Advance(int count)
+    public override void Advance(int count)
     {
     {
         ThrowIfEnded();
         ThrowIfEnded();
 
 
         _buffered += count;
         _buffered += count;
+        _unflushedBytes += count;
         _currentChunk = _currentChunk[count..];
         _currentChunk = _currentChunk[count..];
     }
     }
 
 
-    public Memory<byte> GetMemory(int sizeHint = 0)
+    public override Memory<byte> GetMemory(int sizeHint = 0)
     {
     {
         ThrowIfEnded();
         ThrowIfEnded();
 
 
@@ -71,9 +82,14 @@ internal sealed class ChunkedBufferWriter<TWriter> : IBufferWriter<byte> where T
         return _currentChunk;
         return _currentChunk;
     }
     }
 
 
-    public Span<byte> GetSpan(int sizeHint = 0) => GetMemory(sizeHint).Span;
+    public override Span<byte> GetSpan(int sizeHint = 0) => GetMemory(sizeHint).Span;
+
+    public override void CancelPendingFlush()
+    {
+        _output.CancelPendingFlush();
+    }
 
 
-    public void End()
+    public override void Complete(Exception exception = null)
     {
     {
         ThrowIfEnded();
         ThrowIfEnded();
 
 
@@ -82,6 +98,17 @@ internal sealed class ChunkedBufferWriter<TWriter> : IBufferWriter<byte> where T
         _ended = true;
         _ended = true;
     }
     }
 
 
+    public override ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = default)
+    {
+        CommitCurrentChunk(isFinal: false);
+
+        var flushTask = _output.FlushAsync(cancellationToken);
+
+        _unflushedBytes = 0;
+
+        return flushTask;
+    }
+
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     [MethodImpl(MethodImplOptions.AggressiveInlining)]
     private static StandardFormat GetHexFormat(int maxValue)
     private static StandardFormat GetHexFormat(int maxValue)
     {
     {

+ 2 - 6
frameworks/CSharp/aspnetcore/src/Platform/Platform.csproj

@@ -6,7 +6,6 @@
     <IsTestAssetProject>true</IsTestAssetProject>
     <IsTestAssetProject>true</IsTestAssetProject>
     <LangVersion>preview</LangVersion>
     <LangVersion>preview</LangVersion>
     <UserSecretsId>38063504-d08c-495a-89c9-daaad2f60f31</UserSecretsId>
     <UserSecretsId>38063504-d08c-495a-89c9-daaad2f60f31</UserSecretsId>
-    <DefineConstants Condition="'$(PublishAot)' == 'true'">AOT;$(DefineConstants)</DefineConstants>
   </PropertyGroup>
   </PropertyGroup>
 
 
   <PropertyGroup>
   <PropertyGroup>
@@ -19,15 +18,12 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Npgsql" Version="8.0.5" />
+    <PackageReference Include="Npgsql" Version="9.0.2" />
     <PackageReference Include="MySqlConnector" Version="2.3.7" />
     <PackageReference Include="MySqlConnector" Version="2.3.7" />
     <PackageReference Include="Dapper" Version="2.1.35" />
     <PackageReference Include="Dapper" Version="2.1.35" />
-    <PackageReference Include="RazorSlices" Version="0.7.0" Condition="$(PublishAot) != 'true'" />
+    <PackageReference Include="RazorSlices" Version="0.8.1" />
   </ItemGroup>
   </ItemGroup>
 
 
-  <PropertyGroup Condition="$(PublishAot) == 'true'">
-    <DefaultItemExcludes>$(MSBuildThisFileDirectory)Templates/**;$(DefaultItemExcludes)</DefaultItemExcludes>
-  </PropertyGroup>
   <ItemGroup Condition="$(PublishAot) == 'true'">
   <ItemGroup Condition="$(PublishAot) == 'true'">
     <RuntimeHostConfigurationOption Include="System.Threading.ThreadPool.HillClimbing.Disable" Value="true" />
     <RuntimeHostConfigurationOption Include="System.Threading.ThreadPool.HillClimbing.Disable" Value="true" />
   </ItemGroup>
   </ItemGroup>