diff --git a/build/dependencies.props b/build/dependencies.props index dd71368d5..890bf8b0e 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -42,7 +42,7 @@ 10.0.0-* 2.1.35 - 0.7.0 + 0.8.1 2.0.0-beta4.22272.1 0.2.0-alpha.24114.2 1.9.0 diff --git a/src/Benchmarks/Benchmarks.csproj b/src/Benchmarks/Benchmarks.csproj index a54490711..329c6f15d 100644 --- a/src/Benchmarks/Benchmarks.csproj +++ b/src/Benchmarks/Benchmarks.csproj @@ -62,7 +62,7 @@ - + diff --git a/src/BenchmarksApps.sln b/src/BenchmarksApps.sln index df1921ad0..22ed89b00 100644 --- a/src/BenchmarksApps.sln +++ b/src/BenchmarksApps.sln @@ -266,12 +266,12 @@ Global {8DF3A6BB-E8C5-4FAA-839A-D185C9F93CD5} = {D8A014FB-3C99-4831-9FFB-F4A89A48D8BD} {9E4AF835-2A78-4012-B0B5-9DA41ACDC716} = {D8A014FB-3C99-4831-9FFB-F4A89A48D8BD} {20757830-EA66-4962-BDBB-A101A2062A2C} = {D8A014FB-3C99-4831-9FFB-F4A89A48D8BD} - {07C0B18B-9738-4349-A8DF-3E88D3DF90AE} = {9E4AF835-2A78-4012-B0B5-9DA41ACDC716} + {07C0B18B-9738-4349-A8DF-3E88D3DF90AE} = {B6DB234C-8F80-4160-B95D-D70AFC444A3D} {E68B58F8-40EA-49EA-A126-0B67F2BE7343} = {B6DB234C-8F80-4160-B95D-D70AFC444A3D} {ACA43671-AD28-4F72-AAAB-6C32B388C2F0} = {B6DB234C-8F80-4160-B95D-D70AFC444A3D} {D8F11F87-823F-4864-926D-5F66448A5C13} = {B6DB234C-8F80-4160-B95D-D70AFC444A3D} {3D2573DE-CE7A-4CB8-A980-8C8636EE059E} = {398A40DA-FE1D-4B4D-A580-A33E29885553} - {31B61CD7-4CF6-464F-B418-04C700A17CB9} = {6A69DE6C-07A6-4ABE-A4D2-0F983A33BBF8} + {31B61CD7-4CF6-464F-B418-04C700A17CB9} = {B6DB234C-8F80-4160-B95D-D70AFC444A3D} {D6616E03-A2DA-4929-AD28-595ECC4C004D} = {B6DB234C-8F80-4160-B95D-D70AFC444A3D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/src/BenchmarksApps/DistributedCache/DistributedCache.csproj b/src/BenchmarksApps/DistributedCache/DistributedCache.csproj index 0469e9620..561258d5d 100644 --- a/src/BenchmarksApps/DistributedCache/DistributedCache.csproj +++ b/src/BenchmarksApps/DistributedCache/DistributedCache.csproj @@ -1,6 +1,6 @@ - + - net7.0 + net8.0 Exe enable enable diff --git a/src/BenchmarksApps/Grpc/GrpcHttpApiServer/Server/Program.cs b/src/BenchmarksApps/Grpc/GrpcHttpApiServer/Server/Program.cs index f0b85f6af..8fb22b55c 100644 --- a/src/BenchmarksApps/Grpc/GrpcHttpApiServer/Server/Program.cs +++ b/src/BenchmarksApps/Grpc/GrpcHttpApiServer/Server/Program.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Console; namespace Server { @@ -84,7 +85,10 @@ public static IHostBuilder CreateHostBuilder(string[] args) if (Enum.TryParse(config["LogLevel"], out var logLevel)) { Console.WriteLine($"Console Logging enabled with level '{logLevel}'"); - loggerFactory.AddConsole(o => o.TimestampFormat = "ss.ffff ").SetMinimumLevel(logLevel); + loggerFactory + .AddConsole() + .AddConsoleFormatter(o => o.TimestampFormat = "ss.ffff") + .SetMinimumLevel(logLevel); } }) .UseDefaultServiceProvider((context, options) => diff --git a/src/BenchmarksApps/Grpc/GrpcHttpApiServer/Server/Server.csproj b/src/BenchmarksApps/Grpc/GrpcHttpApiServer/Server/Server.csproj index 49a4b9e2d..5a6683d22 100644 --- a/src/BenchmarksApps/Grpc/GrpcHttpApiServer/Server/Server.csproj +++ b/src/BenchmarksApps/Grpc/GrpcHttpApiServer/Server/Server.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/BenchmarksApps/HelloWorldMvc/HelloWorldMvc.csproj b/src/BenchmarksApps/HelloWorldMvc/HelloWorldMvc.csproj index 24f1b8ea7..acfe3326f 100644 --- a/src/BenchmarksApps/HelloWorldMvc/HelloWorldMvc.csproj +++ b/src/BenchmarksApps/HelloWorldMvc/HelloWorldMvc.csproj @@ -1,6 +1,6 @@ - + - net7.0 + net8.0 Exe \ No newline at end of file diff --git a/src/BenchmarksApps/MapAction/MapAction.csproj b/src/BenchmarksApps/MapAction/MapAction.csproj index cceeead16..c70916d37 100644 --- a/src/BenchmarksApps/MapAction/MapAction.csproj +++ b/src/BenchmarksApps/MapAction/MapAction.csproj @@ -1,6 +1,6 @@ - + - net7.0 + net8.0 diff --git a/src/BenchmarksApps/SignalR/BenchmarkServer.csproj b/src/BenchmarksApps/SignalR/BenchmarkServer.csproj index 60beb1ef5..578936fdc 100644 --- a/src/BenchmarksApps/SignalR/BenchmarkServer.csproj +++ b/src/BenchmarksApps/SignalR/BenchmarkServer.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 diff --git a/src/BenchmarksApps/TechEmpower/BlazorSSR/BlazorSSR.csproj b/src/BenchmarksApps/TechEmpower/BlazorSSR/BlazorSSR.csproj index 5407bdc2e..8c0fa575a 100644 --- a/src/BenchmarksApps/TechEmpower/BlazorSSR/BlazorSSR.csproj +++ b/src/BenchmarksApps/TechEmpower/BlazorSSR/BlazorSSR.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj b/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj index f03e6fb94..646892e45 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj +++ b/src/BenchmarksApps/TechEmpower/Minimal/Minimal.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 enable enable latest @@ -14,7 +14,7 @@ - + diff --git a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs index 287f7a035..9b0d160e4 100644 --- a/src/BenchmarksApps/TechEmpower/Minimal/Program.cs +++ b/src/BenchmarksApps/TechEmpower/Minimal/Program.cs @@ -5,6 +5,7 @@ using Minimal; using Minimal.Database; using Minimal.Models; +using Minimal.Templates; var builder = WebApplication.CreateBuilder(args); @@ -37,12 +38,11 @@ app.MapGet("/db/result", async (Db db) => Results.Json(await db.LoadSingleQueryRow())); -var createFortunesTemplate = RazorSlice.ResolveSliceFactory>("/Templates/Fortunes.cshtml"); var htmlEncoder = CreateHtmlEncoder(); app.MapGet("/fortunes", async (HttpContext context, Db db) => { var fortunes = await db.LoadFortunesRows(); - var template = (RazorSliceHttpResult>)createFortunesTemplate(fortunes); + var template = (RazorSliceHttpResult>)Fortunes.Create(fortunes); template.HtmlEncoder = htmlEncoder; return template; }); diff --git a/src/BenchmarksApps/TechEmpower/Mvc/Mvc.csproj b/src/BenchmarksApps/TechEmpower/Mvc/Mvc.csproj index ea4a47118..f7b308ee1 100644 --- a/src/BenchmarksApps/TechEmpower/Mvc/Mvc.csproj +++ b/src/BenchmarksApps/TechEmpower/Mvc/Mvc.csproj @@ -11,17 +11,12 @@ - - - - - - + diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Caching.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Caching.cs index 24896e59b..015125050 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Caching.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Caching.cs @@ -6,7 +6,7 @@ namespace PlatformBenchmarks { - public partial class BenchmarkApplication + public sealed partial class BenchmarkApplication { private static async Task Caching(PipeWriter pipeWriter, int count) { diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs index 7f09d3e3c..5049e4630 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs @@ -1,81 +1,81 @@ // 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. +using System; using System.IO.Pipelines; using System.Runtime.CompilerServices; using System.Threading.Tasks; using RazorSlices; -namespace PlatformBenchmarks +namespace PlatformBenchmarks; + +public sealed partial class BenchmarkApplication { - public partial class BenchmarkApplication - { #if DATABASE - private async Task FortunesRaw(PipeWriter pipeWriter) - { - await OutputFortunes( - pipeWriter, - await RawDb.LoadFortunesRows(), - // To isolate template rendering from DB access, comment out the line above and uncomment the line below - //await RawDb.LoadFortunesRowsNoDb(), - FortunesTemplateFactory); - } - - private async Task FortunesDapper(PipeWriter pipeWriter) - { - await OutputFortunes(pipeWriter, await DapperDb.LoadFortunesRows(), FortunesDapperTemplateFactory); - } - - private async Task FortunesEf(PipeWriter pipeWriter) - { - await OutputFortunes(pipeWriter, await EfDb.LoadFortunesRows(), FortunesEfTemplateFactory); - } + private async Task FortunesRaw(PipeWriter pipeWriter) + { + await OutputFortunes( + pipeWriter, + await RawDb.LoadFortunesRows(), + // To isolate template rendering from DB access, comment out the line above and uncomment the line below + //await RawDb.LoadFortunesRowsNoDb(), + Templates.FortunesUtf8.Create); + } - private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, SliceFactory templateFactory) - { - // Render headers - var preamble = """ - HTTP/1.1 200 OK - Server: K - Content-Type: text/html; charset=utf-8 - Transfer-Encoding: chunked - """u8; - var headersLength = preamble.Length + DateHeader.HeaderBytes.Length; - var headersSpan = pipeWriter.GetSpan(headersLength); - preamble.CopyTo(headersSpan); - DateHeader.HeaderBytes.CopyTo(headersSpan[preamble.Length..]); - pipeWriter.Advance(headersLength); + private async Task FortunesDapper(PipeWriter pipeWriter) + { + await OutputFortunes(pipeWriter, await DapperDb.LoadFortunesRows(), Templates.FortunesUtf8.Create); + } - // Render body - var template = templateFactory(model); - // 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. - var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSizeHint: 2048); - var renderTask = template.RenderAsync(chunkedWriter, null, HtmlEncoder); + private async Task FortunesEf(PipeWriter pipeWriter) + { + await OutputFortunes(pipeWriter, await EfDb.LoadFortunesRows(), Templates.FortunesEf.Create); + } - if (renderTask.IsCompletedSuccessfully) - { - renderTask.GetAwaiter().GetResult(); - EndTemplateRendering(chunkedWriter, template); - return ValueTask.CompletedTask; - } + private ValueTask OutputFortunes(PipeWriter pipeWriter, TModel model, Func templateFactory) + { + // Render headers + var preamble = """ + HTTP/1.1 200 OK + Server: K + Content-Type: text/html; charset=utf-8 + Transfer-Encoding: chunked + """u8; + var headersLength = preamble.Length + DateHeader.HeaderBytes.Length; + var headersSpan = pipeWriter.GetSpan(headersLength); + preamble.CopyTo(headersSpan); + DateHeader.HeaderBytes.CopyTo(headersSpan[preamble.Length..]); + pipeWriter.Advance(headersLength); - return AwaitTemplateRenderTask(renderTask, chunkedWriter, template); - } + // Render body + var template = templateFactory(model); + // 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. + var chunkedWriter = GetChunkedWriter(pipeWriter, chunkSizeHint: 2048); + var renderTask = template.RenderAsync(chunkedWriter, HtmlEncoder); - private static async ValueTask AwaitTemplateRenderTask(ValueTask renderTask, ChunkedBufferWriter chunkedWriter, RazorSlice template) + if (renderTask.IsCompletedSuccessfully) { - await renderTask; + renderTask.GetAwaiter().GetResult(); EndTemplateRendering(chunkedWriter, template); + return ValueTask.CompletedTask; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void EndTemplateRendering(ChunkedBufferWriter chunkedWriter, RazorSlice template) - { - chunkedWriter.End(); - ReturnChunkedWriter(chunkedWriter); - template.Dispose(); - } -#endif + return AwaitTemplateRenderTask(renderTask, chunkedWriter, template); } + + private static async ValueTask AwaitTemplateRenderTask(ValueTask renderTask, ChunkedPipeWriter chunkedWriter, RazorSlice template) + { + await renderTask; + EndTemplateRendering(chunkedWriter, template); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void EndTemplateRendering(ChunkedPipeWriter chunkedWriter, RazorSlice template) + { + chunkedWriter.Complete(); + ReturnChunkedWriter(chunkedWriter); + template.Dispose(); + } +#endif } diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs index 11f6c782a..f9066761f 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.HttpConnection.cs @@ -13,7 +13,7 @@ namespace PlatformBenchmarks { - public partial class BenchmarkApplication : IHttpConnection + public sealed partial class BenchmarkApplication : IHttpConnection { private State _state; @@ -258,54 +258,44 @@ private static BufferWriter GetWriter(PipeWriter pipeWriter, int => new(new(pipeWriter), sizeHint); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ChunkedBufferWriter GetChunkedWriter(PipeWriter pipeWriter, int chunkSizeHint) + private static ChunkedPipeWriter GetChunkedWriter(PipeWriter pipeWriter, int chunkSizeHint) { var writer = ChunkedWriterPool.Get(); - writer.SetOutput(new WriterAdapter(pipeWriter), chunkSizeHint); + writer.SetOutput(pipeWriter, chunkSizeHint); return writer; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ReturnChunkedWriter(ChunkedBufferWriter writer) => ChunkedWriterPool.Return(writer); + private static void ReturnChunkedWriter(ChunkedPipeWriter writer) => ChunkedWriterPool.Return(writer); - private struct WriterAdapter : IBufferWriter + private readonly struct WriterAdapter(PipeWriter writer) : IBufferWriter { - public PipeWriter Writer; + public readonly void Advance(int count) + => writer.Advance(count); - public WriterAdapter(PipeWriter writer) - => Writer = writer; + public readonly Memory GetMemory(int sizeHint = 0) + => writer.GetMemory(sizeHint); - public void Advance(int count) - => Writer.Advance(count); - - public Memory GetMemory(int sizeHint = 0) - => Writer.GetMemory(sizeHint); - - public Span GetSpan(int sizeHint = 0) - => Writer.GetSpan(sizeHint); + public readonly Span GetSpan(int sizeHint = 0) + => writer.GetSpan(sizeHint); } - private struct ParsingAdapter : IHttpRequestLineHandler, IHttpHeadersHandler + private readonly struct ParsingAdapter(BenchmarkApplication requestHandler) : IHttpRequestLineHandler, IHttpHeadersHandler { - public BenchmarkApplication RequestHandler; - - public ParsingAdapter(BenchmarkApplication requestHandler) - => RequestHandler = requestHandler; - - public void OnStaticIndexedHeader(int index) - => RequestHandler.OnStaticIndexedHeader(index); + public readonly void OnStaticIndexedHeader(int index) + => requestHandler.OnStaticIndexedHeader(index); - public void OnStaticIndexedHeader(int index, ReadOnlySpan value) - => RequestHandler.OnStaticIndexedHeader(index, value); + public readonly void OnStaticIndexedHeader(int index, ReadOnlySpan value) + => requestHandler.OnStaticIndexedHeader(index, value); - public void OnHeader(ReadOnlySpan name, ReadOnlySpan value) - => RequestHandler.OnHeader(name, value); + public readonly void OnHeader(ReadOnlySpan name, ReadOnlySpan value) + => requestHandler.OnHeader(name, value); - public void OnHeadersComplete(bool endStream) - => RequestHandler.OnHeadersComplete(endStream); + public readonly void OnHeadersComplete(bool endStream) + => requestHandler.OnHeadersComplete(endStream); - public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span startLine) - => RequestHandler.OnStartLine(versionAndMethod, targetPath, startLine); + public readonly void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span startLine) + => requestHandler.OnStartLine(versionAndMethod, targetPath, startLine); } } } diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Json.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Json.cs index f196bba23..3d973bdb5 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Json.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Json.cs @@ -7,7 +7,7 @@ namespace PlatformBenchmarks { - public partial class BenchmarkApplication + public sealed partial class BenchmarkApplication { #if !DATABASE private static ReadOnlySpan _jsonPreamble => diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.MultipleQueries.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.MultipleQueries.cs index d7fd1bd12..961bfc249 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.MultipleQueries.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.MultipleQueries.cs @@ -8,7 +8,7 @@ namespace PlatformBenchmarks { - public partial class BenchmarkApplication + public sealed partial class BenchmarkApplication { private static async Task MultipleQueries(PipeWriter pipeWriter, int count) { diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Plaintext.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Plaintext.cs index 5f9edb358..90c9fab48 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Plaintext.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Plaintext.cs @@ -5,7 +5,7 @@ namespace PlatformBenchmarks { - public partial class BenchmarkApplication + public sealed partial class BenchmarkApplication { private static ReadOnlySpan _plaintextPreamble => "HTTP/1.1 200 OK\r\n"u8 + diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs index 5c00d87c8..f273a8192 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.SingleQuery.cs @@ -7,7 +7,7 @@ namespace PlatformBenchmarks { - public partial class BenchmarkApplication + public sealed partial class BenchmarkApplication { private static async Task SingleQuery(PipeWriter pipeWriter) { diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Updates.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Updates.cs index 2d5243649..d3cf8c561 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Updates.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Updates.cs @@ -7,7 +7,7 @@ namespace PlatformBenchmarks { - public partial class BenchmarkApplication + public sealed partial class BenchmarkApplication { private static async Task Updates(PipeWriter pipeWriter, int count) { diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs index cc50a9b6b..816d03a61 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.cs @@ -10,216 +10,208 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http; using Microsoft.Extensions.ObjectPool; -using RazorSlices; -namespace PlatformBenchmarks +namespace PlatformBenchmarks; + +public sealed partial class BenchmarkApplication { - public sealed partial class BenchmarkApplication + public static ReadOnlySpan ApplicationName => "Kestrel Platform-Level Application"u8; + + private static ReadOnlySpan _crlf => "\r\n"u8; + private static ReadOnlySpan _eoh => "\r\n\r\n"u8; // End Of Headers + private static ReadOnlySpan _http11OK => "HTTP/1.1 200 OK\r\n"u8; + private static ReadOnlySpan _http11NotFound => "HTTP/1.1 404 Not Found\r\n"u8; + private static ReadOnlySpan _headerServer => "Server: K"u8; + private static ReadOnlySpan _headerContentLength => "Content-Length: "u8; + private static ReadOnlySpan _headerContentLengthZero => "Content-Length: 0"u8; + private static ReadOnlySpan _headerContentTypeText => "Content-Type: text/plain"u8; + private static ReadOnlySpan _headerContentTypeJson => "Content-Type: application/json"u8; + private static ReadOnlySpan _headerContentTypeHtml => "Content-Type: text/html; charset=UTF-8"u8; + + private static ReadOnlySpan _dbPreamble => + "HTTP/1.1 200 OK\r\n"u8 + + "Server: K\r\n"u8 + + "Content-Type: application/json\r\n"u8 + + "Content-Length: "u8; + + private static ReadOnlySpan _plainTextBody => "Hello, World!"u8; + private static ReadOnlySpan _contentLengthGap => " "u8; + + public static RawDb RawDb { get; set; } + public static DapperDb DapperDb { get; set; } + public static EfDb EfDb { get; set; } + + private static readonly DefaultObjectPool ChunkedWriterPool + = new(new ChunkedWriterObjectPolicy()); + + private sealed class ChunkedWriterObjectPolicy : IPooledObjectPolicy { - public static ReadOnlySpan ApplicationName => "Kestrel Platform-Level Application"u8; - - private static ReadOnlySpan _crlf => "\r\n"u8; - private static ReadOnlySpan _eoh => "\r\n\r\n"u8; // End Of Headers - private static ReadOnlySpan _http11OK => "HTTP/1.1 200 OK\r\n"u8; - private static ReadOnlySpan _http11NotFound => "HTTP/1.1 404 Not Found\r\n"u8; - private static ReadOnlySpan _headerServer => "Server: K"u8; - private static ReadOnlySpan _headerContentLength => "Content-Length: "u8; - private static ReadOnlySpan _headerContentLengthZero => "Content-Length: 0"u8; - private static ReadOnlySpan _headerContentTypeText => "Content-Type: text/plain"u8; - private static ReadOnlySpan _headerContentTypeJson => "Content-Type: application/json"u8; - private static ReadOnlySpan _headerContentTypeHtml => "Content-Type: text/html; charset=UTF-8"u8; - - private static ReadOnlySpan _dbPreamble => - "HTTP/1.1 200 OK\r\n"u8 + - "Server: K\r\n"u8 + - "Content-Type: application/json\r\n"u8 + - "Content-Length: "u8; - - private static ReadOnlySpan _plainTextBody => "Hello, World!"u8; - private static ReadOnlySpan _contentLengthGap => " "u8; - - public static RawDb RawDb { get; set; } - public static DapperDb DapperDb { get; set; } - public static EfDb EfDb { get; set; } - - private static readonly DefaultObjectPool> ChunkedWriterPool - = new(new ChunkedWriterObjectPolicy()); - - private sealed class ChunkedWriterObjectPolicy : IPooledObjectPolicy> - { - public ChunkedBufferWriter Create() => new(); + public ChunkedPipeWriter Create() => new(); - public bool Return(ChunkedBufferWriter writer) - { - writer.Reset(); - return true; - } + public bool Return(ChunkedPipeWriter writer) + { + writer.Reset(); + return true; } + } -#if DATABASE - private readonly static SliceFactory> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/FortunesUtf8.cshtml"); - private readonly static SliceFactory> FortunesDapperTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/FortunesUtf16.cshtml"); - private readonly static SliceFactory> FortunesEfTemplateFactory = RazorSlice.ResolveSliceFactory>("/Templates/FortunesEf.cshtml"); -#endif + [ThreadStatic] + private static Utf8JsonWriter t_writer; - [ThreadStatic] - private static Utf8JsonWriter t_writer; + private static readonly JsonContext SerializerContext = JsonContext.Default; - private static readonly JsonContext SerializerContext = JsonContext.Default; + [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] + [JsonSerializable(typeof(JsonMessage))] + [JsonSerializable(typeof(CachedWorld[]))] + [JsonSerializable(typeof(World[]))] + private partial class JsonContext : JsonSerializerContext + { + } - [JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization)] - [JsonSerializable(typeof(JsonMessage))] - [JsonSerializable(typeof(CachedWorld[]))] - [JsonSerializable(typeof(World[]))] - private partial class JsonContext : JsonSerializerContext - { - } + public static class Paths + { + public static ReadOnlySpan Json => "/json"u8; + public static ReadOnlySpan Plaintext => "/plaintext"u8; + public static ReadOnlySpan SingleQuery => "/db"u8; + public static ReadOnlySpan FortunesRaw => "/fortunes"u8; + public static ReadOnlySpan FortunesDapper => "/fortunes/dapper"u8; + public static ReadOnlySpan FortunesEf => "/fortunes/ef"u8; + public static ReadOnlySpan Updates => "/updates/"u8; + public static ReadOnlySpan MultipleQueries => "/queries/"u8; + public static ReadOnlySpan Caching => "/cached-worlds/"u8; + } - public static class Paths - { - public static ReadOnlySpan Json => "/json"u8; - public static ReadOnlySpan Plaintext => "/plaintext"u8; - public static ReadOnlySpan SingleQuery => "/db"u8; - public static ReadOnlySpan FortunesRaw => "/fortunes"u8; - public static ReadOnlySpan FortunesDapper => "/fortunes/dapper"u8; - public static ReadOnlySpan FortunesEf => "/fortunes/ef"u8; - public static ReadOnlySpan Updates => "/updates/"u8; - public static ReadOnlySpan MultipleQueries => "/queries/"u8; - public static ReadOnlySpan Caching => "/cached-worlds/"u8; - } + private RequestType _requestType; + private int _queries; - private RequestType _requestType; - private int _queries; + public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span startLine) + { + _requestType = versionAndMethod.Method == HttpMethod.Get ? GetRequestType(startLine.Slice(targetPath.Offset, targetPath.Length), ref _queries) : RequestType.NotRecognized; + } - public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span startLine) + private static RequestType GetRequestType(ReadOnlySpan path, ref int queries) + { +#if !DATABASE + if (path.Length == 10 && path.SequenceEqual(Paths.Plaintext)) { - _requestType = versionAndMethod.Method == HttpMethod.Get ? GetRequestType(startLine.Slice(targetPath.Offset, targetPath.Length), ref _queries) : RequestType.NotRecognized; + return RequestType.PlainText; } - - private static RequestType GetRequestType(ReadOnlySpan path, ref int queries) + else if (path.Length == 5 && path.SequenceEqual(Paths.Json)) { -#if !DATABASE - if (path.Length == 10 && path.SequenceEqual(Paths.Plaintext)) - { - return RequestType.PlainText; - } - else if (path.Length == 5 && path.SequenceEqual(Paths.Json)) - { - return RequestType.Json; - } + return RequestType.Json; + } #else - if (path.Length == 3 && path[0] == '/' && path[1] == 'd' && path[2] == 'b') - { - return RequestType.SingleQuery; - } - if (path[1] == 'f') - { - return path.Length switch - { - 9 when path.SequenceEqual(Paths.FortunesRaw) => RequestType.FortunesRaw, - 16 when path.SequenceEqual(Paths.FortunesDapper) => RequestType.FortunesDapper, - 12 when path.SequenceEqual(Paths.FortunesEf) => RequestType.FortunesEf, - _ => RequestType.NotRecognized - }; - } - if (path.Length >= 15 && path[1] == 'c' && path.StartsWith(Paths.Caching)) - { - queries = ParseQueries(path.Slice(15)); - return RequestType.Caching; - } - if (path.Length >= 9 && path[1] == 'u' && path.StartsWith(Paths.Updates)) - { - queries = ParseQueries(path.Slice(9)); - return RequestType.Updates; - } - if (path.Length >= 9 && path[1] == 'q' && path.StartsWith(Paths.MultipleQueries)) + if (path.Length == 3 && path[0] == '/' && path[1] == 'd' && path[2] == 'b') + { + return RequestType.SingleQuery; + } + if (path[1] == 'f') + { + return path.Length switch { - queries = ParseQueries(path.Slice(9)); - return RequestType.MultipleQueries; - } -#endif - return RequestType.NotRecognized; + 9 when path.SequenceEqual(Paths.FortunesRaw) => RequestType.FortunesRaw, + 16 when path.SequenceEqual(Paths.FortunesDapper) => RequestType.FortunesDapper, + 12 when path.SequenceEqual(Paths.FortunesEf) => RequestType.FortunesEf, + _ => RequestType.NotRecognized + }; + } + if (path.Length >= 15 && path[1] == 'c' && path.StartsWith(Paths.Caching)) + { + queries = ParseQueries(path.Slice(15)); + return RequestType.Caching; + } + if (path.Length >= 9 && path[1] == 'u' && path.StartsWith(Paths.Updates)) + { + queries = ParseQueries(path.Slice(9)); + return RequestType.Updates; } + if (path.Length >= 9 && path[1] == 'q' && path.StartsWith(Paths.MultipleQueries)) + { + queries = ParseQueries(path.Slice(9)); + return RequestType.MultipleQueries; + } +#endif + return RequestType.NotRecognized; + } #if !DATABASE - private void ProcessRequest(ref BufferWriter writer) + private void ProcessRequest(ref BufferWriter writer) + { + if (_requestType == RequestType.PlainText) { - if (_requestType == RequestType.PlainText) - { - PlainText(ref writer); - } - else if (_requestType == RequestType.Json) - { - Json(ref writer, Writer); - } - else - { - Default(ref writer); - } + PlainText(ref writer); } -#else - - private static int ParseQueries(ReadOnlySpan parameter) + else if (_requestType == RequestType.Json) { - if (!Utf8Parser.TryParse(parameter, out int queries, out _)) - { - queries = 1; - } - else - { - queries = Math.Clamp(queries, 1, 500); - } - - return queries; + Json(ref writer, Writer); } - - private Task ProcessRequestAsync() => _requestType switch - { - RequestType.FortunesRaw => FortunesRaw(Writer), - RequestType.FortunesDapper => FortunesDapper(Writer), - RequestType.FortunesEf => FortunesEf(Writer), - RequestType.SingleQuery => SingleQuery(Writer), - RequestType.Caching => Caching(Writer, _queries), - RequestType.Updates => Updates(Writer, _queries), - RequestType.MultipleQueries => MultipleQueries(Writer, _queries), - _ => Default(Writer) - }; - - private static Task Default(PipeWriter pipeWriter) + else { - var writer = GetWriter(pipeWriter, sizeHint: _defaultPreamble.Length + DateHeader.HeaderBytes.Length); Default(ref writer); - writer.Commit(); - return Task.CompletedTask; } -#endif - private static ReadOnlySpan _defaultPreamble => - "HTTP/1.1 200 OK\r\n"u8 + - "Server: K"u8 + "\r\n"u8 + - "Content-Type: text/plain"u8 + - "Content-Length: 0"u8; + } +#else - private static void Default(ref BufferWriter writer) + private static int ParseQueries(ReadOnlySpan parameter) + { + if (!Utf8Parser.TryParse(parameter, out int queries, out _)) { - writer.Write(_defaultPreamble); - - // Date header - writer.Write(DateHeader.HeaderBytes); + queries = 1; } - - private enum RequestType + else { - NotRecognized, - PlainText, - Json, - FortunesRaw, - FortunesDapper, - FortunesEf, - SingleQuery, - Caching, - Updates, - MultipleQueries + queries = Math.Clamp(queries, 1, 500); } + + return queries; + } + + private Task ProcessRequestAsync() => _requestType switch + { + RequestType.FortunesRaw => FortunesRaw(Writer), + RequestType.FortunesDapper => FortunesDapper(Writer), + RequestType.FortunesEf => FortunesEf(Writer), + RequestType.SingleQuery => SingleQuery(Writer), + RequestType.Caching => Caching(Writer, _queries), + RequestType.Updates => Updates(Writer, _queries), + RequestType.MultipleQueries => MultipleQueries(Writer, _queries), + _ => Default(Writer) + }; + + private static Task Default(PipeWriter pipeWriter) + { + var writer = GetWriter(pipeWriter, sizeHint: _defaultPreamble.Length + DateHeader.HeaderBytes.Length); + Default(ref writer); + writer.Commit(); + return Task.CompletedTask; + } +#endif + private static ReadOnlySpan _defaultPreamble => + "HTTP/1.1 200 OK\r\n"u8 + + "Server: K"u8 + "\r\n"u8 + + "Content-Type: text/plain"u8 + + "Content-Length: 0"u8; + + private static void Default(ref BufferWriter writer) + { + writer.Write(_defaultPreamble); + + // Date header + writer.Write(DateHeader.HeaderBytes); + } + + private enum RequestType + { + NotRecognized, + PlainText, + Json, + FortunesRaw, + FortunesDapper, + FortunesEf, + SingleQuery, + Caching, + Updates, + MultipleQueries } } diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedPipeWriter.cs similarity index 85% rename from src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs rename to src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedPipeWriter.cs index 66c74b964..5f6a378f7 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedBufferWriter.cs +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/ChunkedPipeWriter.cs @@ -2,35 +2,40 @@ using System.Buffers; using System.Buffers.Text; using System.Diagnostics; +using System.IO.Pipelines; using System.Numerics; using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; namespace PlatformBenchmarks; -internal sealed class ChunkedBufferWriter : IBufferWriter where TWriter : IBufferWriter +internal sealed class ChunkedPipeWriter : PipeWriter { private const int DefaultChunkSizeHint = 2048; private static readonly StandardFormat DefaultHexFormat = GetHexFormat(DefaultChunkSizeHint); private static ReadOnlySpan ChunkTerminator => "\r\n"u8; - private TWriter _output; + private PipeWriter _output; private int _chunkSizeHint; private StandardFormat _hexFormat = DefaultHexFormat; private Memory _currentFullChunk; private Memory _currentChunk; private int _buffered; + private long _unflushedBytes; private bool _ended = false; public Memory Memory => _currentChunk; - public TWriter Output => _output; + public PipeWriter Output => _output; public int Buffered => _buffered; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SetOutput(TWriter output, int chunkSizeHint = DefaultChunkSizeHint) + public void SetOutput(PipeWriter output, int chunkSizeHint = DefaultChunkSizeHint) { _buffered = 0; + _unflushedBytes = 0; _chunkSizeHint = chunkSizeHint; _output = output; @@ -41,6 +46,7 @@ public void SetOutput(TWriter output, int chunkSizeHint = DefaultChunkSizeHint) public void Reset() { _buffered = 0; + _unflushedBytes = 0; _output = default; _ended = false; _hexFormat = DefaultHexFormat; @@ -48,16 +54,21 @@ public void Reset() _currentChunk = default; } + public override bool CanGetUnflushedBytes => true; + + public override long UnflushedBytes => _unflushedBytes; + [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Advance(int count) + public override void Advance(int count) { ThrowIfEnded(); _buffered += count; + _unflushedBytes += count; _currentChunk = _currentChunk[count..]; } - public Memory GetMemory(int sizeHint = 0) + public override Memory GetMemory(int sizeHint = 0) { ThrowIfEnded(); @@ -68,9 +79,14 @@ public Memory GetMemory(int sizeHint = 0) return _currentChunk; } - public Span GetSpan(int sizeHint = 0) => GetMemory(sizeHint).Span; + public override Span GetSpan(int sizeHint = 0) => GetMemory(sizeHint).Span; + + public override void CancelPendingFlush() + { + _output.CancelPendingFlush(); + } - public void End() + public override void Complete(Exception exception = null) { ThrowIfEnded(); @@ -79,6 +95,17 @@ public void End() _ended = true; } + public override ValueTask FlushAsync(CancellationToken cancellationToken = default) + { + CommitCurrentChunk(isFinal: false); + + var flushTask = _output.FlushAsync(cancellationToken); + + _unflushedBytes = 0; + + return flushTask; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static StandardFormat GetHexFormat(int maxValue) { diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj index 0a5159f33..d7c3442de 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/PlatformBenchmarks.csproj @@ -1,7 +1,7 @@  - net7.0;net8.0 + net8.0;net9.0 Exe true true @@ -22,19 +22,14 @@ - - - - - - + - + diff --git a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Properties/launchSettings.json b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Properties/launchSettings.json index 89d1445f7..b6dbfb86f 100644 --- a/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Properties/launchSettings.json +++ b/src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Properties/launchSettings.json @@ -7,7 +7,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "https://localhost:53705;http://localhost:53706" + "applicationUrl": "http://localhost:53705;http://localhost:53706" } } } \ No newline at end of file diff --git a/src/BenchmarksApps/TechEmpower/RazorPages/RazorPages.csproj b/src/BenchmarksApps/TechEmpower/RazorPages/RazorPages.csproj index 92f3d4e14..a72c2b522 100644 --- a/src/BenchmarksApps/TechEmpower/RazorPages/RazorPages.csproj +++ b/src/BenchmarksApps/TechEmpower/RazorPages/RazorPages.csproj @@ -1,7 +1,7 @@ - + - net7.0;net8.0 + net8.0;net9.0 enable enable preview @@ -12,17 +12,12 @@ - - - - - - + diff --git a/src/BenchmarksApps/TodosApi/TodosApi.csproj b/src/BenchmarksApps/TodosApi/TodosApi.csproj index 47d7a541e..fcc66b17b 100644 --- a/src/BenchmarksApps/TodosApi/TodosApi.csproj +++ b/src/BenchmarksApps/TodosApi/TodosApi.csproj @@ -14,7 +14,6 @@ - @@ -28,12 +27,16 @@ - + + + + + - + - + diff --git a/src/Downstream/Downstream.csproj b/src/Downstream/Downstream.csproj index 3f208e3b5..4f9c3c8bc 100644 --- a/src/Downstream/Downstream.csproj +++ b/src/Downstream/Downstream.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 latest diff --git a/src/TcpClient/TcpClient.csproj b/src/TcpClient/TcpClient.csproj index 0f02f80f0..cef235c61 100644 --- a/src/TcpClient/TcpClient.csproj +++ b/src/TcpClient/TcpClient.csproj @@ -2,7 +2,7 @@ Exe - net7.0 + net8.0