Skip to content

Updates to aspnetcore PG database access #8005

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace PlatformBenchmarks;

public partial class BenchmarkApplication
{
private async Task Caching(PipeWriter pipeWriter, int count)
private static async Task Caching(PipeWriter pipeWriter, int count)
{
OutputMultipleQueries(pipeWriter, await Db.LoadCachedQueries(count), SerializerContext.CachedWorldArray);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,63 @@

#if DATABASE

using System.Collections.Generic;
using System.IO.Pipelines;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;
using RazorSlices;

namespace PlatformBenchmarks
{
public partial class BenchmarkApplication
{
private static ReadOnlySpan<byte> _fortunesPreamble =>
"HTTP/1.1 200 OK\r\n"u8 +
"Server: K\r\n"u8 +
"Content-Type: text/html; charset=UTF-8\r\n"u8 +
"Content-Length: "u8;

private async Task Fortunes(PipeWriter pipeWriter)
{
OutputFortunes(pipeWriter, await Db.LoadFortunesRows());
await OutputFortunes(pipeWriter, await Db.LoadFortunesRows(), FortunesTemplateFactory);
}

private void OutputFortunes(PipeWriter pipeWriter, List<Fortune> model)
private ValueTask OutputFortunes<TModel>(PipeWriter pipeWriter, TModel model, SliceFactory<TModel> templateFactory)
{
var writer = GetWriter(pipeWriter, sizeHint: 1600); // in reality it's 1361

writer.Write(_fortunesPreamble);

var lengthWriter = writer;
writer.Write(_contentLengthGap);
// 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);

// Date header
writer.Write(DateHeader.HeaderBytes);
// 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);

var bodyStart = writer.Buffered;
// Body
writer.Write(_fortunesTableStart);
foreach (var item in model)
if (renderTask.IsCompletedSuccessfully)
{
writer.Write(_fortunesRowStart);
writer.WriteNumeric((uint)item.Id);
writer.Write(_fortunesColumn);
writer.WriteUtf8String(HtmlEncoder.Encode(item.Message));
writer.Write(_fortunesRowEnd);
renderTask.GetAwaiter().GetResult();
EndTemplateRendering(chunkedWriter, template);
return ValueTask.CompletedTask;
}
writer.Write(_fortunesTableEnd);
lengthWriter.WriteNumeric((uint)(writer.Buffered - bodyStart));

writer.Commit();
return AwaitTemplateRenderTask(renderTask, chunkedWriter, template);
}

private static async ValueTask AwaitTemplateRenderTask(ValueTask renderTask, ChunkedBufferWriter<WriterAdapter> chunkedWriter, RazorSlice template)
{
await renderTask;
EndTemplateRendering(chunkedWriter, template);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void EndTemplateRendering(ChunkedBufferWriter<WriterAdapter> chunkedWriter, RazorSlice template)
{
chunkedWriter.End();
ReturnChunkedWriter(chunkedWriter);
template.Dispose();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,18 @@ private enum State

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static BufferWriter<WriterAdapter> GetWriter(PipeWriter pipeWriter, int sizeHint)
=> new(new WriterAdapter(pipeWriter), sizeHint);
=> new(new(pipeWriter), sizeHint);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ChunkedBufferWriter<WriterAdapter> GetChunkedWriter(PipeWriter pipeWriter, int chunkSizeHint)
{
var writer = ChunkedWriterPool.Get();
writer.SetOutput(new WriterAdapter(pipeWriter), chunkSizeHint);
return writer;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ReturnChunkedWriter(ChunkedBufferWriter<WriterAdapter> writer) => ChunkedWriterPool.Return(writer);

private struct WriterAdapter : IBufferWriter<byte>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ private static void Json(ref BufferWriter<WriterAdapter> writer, IBufferWriter<b

writer.Commit();

Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(bodyWriter, new JsonWriterOptions { SkipValidation = true });
var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(bodyWriter, new JsonWriterOptions { SkipValidation = true });
utf8JsonWriter.Reset(bodyWriter);

// Body
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace PlatformBenchmarks
{
public partial class BenchmarkApplication
{
private async Task MultipleQueries(PipeWriter pipeWriter, int count)
private static async Task MultipleQueries(PipeWriter pipeWriter, int count)
{
OutputMultipleQueries(pipeWriter, await Db.LoadMultipleQueriesRows(count), SerializerContext.WorldArray);
}
Expand All @@ -31,11 +31,11 @@ private static void OutputMultipleQueries<TWorld>(PipeWriter pipeWriter, TWorld[

writer.Commit();

Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
utf8JsonWriter.Reset(pipeWriter);

// Body
JsonSerializer.Serialize<TWorld[]>(utf8JsonWriter, rows, jsonTypeInfo);
JsonSerializer.Serialize(utf8JsonWriter, rows, jsonTypeInfo);

// Content-Length
lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace PlatformBenchmarks
{
public partial class BenchmarkApplication
{
private async Task SingleQuery(PipeWriter pipeWriter)
private static async Task SingleQuery(PipeWriter pipeWriter)
{
OutputSingleQuery(pipeWriter, await Db.LoadSingleQueryRow());
}
Expand All @@ -30,7 +30,7 @@ private static void OutputSingleQuery(PipeWriter pipeWriter, World row)

writer.Commit();

Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
utf8JsonWriter.Reset(pipeWriter);

// Body
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace PlatformBenchmarks
{
public partial class BenchmarkApplication
{
private async Task Updates(PipeWriter pipeWriter, int count)
private static async Task Updates(PipeWriter pipeWriter, int count)
{
OutputUpdates(pipeWriter, await Db.LoadMultipleUpdatesRows(count));
}
Expand All @@ -30,11 +30,11 @@ private static void OutputUpdates(PipeWriter pipeWriter, World[] rows)

writer.Commit();

Utf8JsonWriter utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
var utf8JsonWriter = t_writer ??= new Utf8JsonWriter(pipeWriter, new JsonWriterOptions { SkipValidation = true });
utf8JsonWriter.Reset(pipeWriter);

// Body
JsonSerializer.Serialize( utf8JsonWriter, rows, SerializerContext.WorldArray);
JsonSerializer.Serialize(utf8JsonWriter, rows, SerializerContext.WorldArray);

// Content-Length
lengthWriter.WriteNumeric((uint)utf8JsonWriter.BytesCommitted);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System.Threading.Tasks;

using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.Extensions.ObjectPool;
using RazorSlices;

namespace PlatformBenchmarks;

Expand All @@ -34,31 +36,49 @@ public sealed partial class BenchmarkApplication
"Content-Length: "u8;

private static ReadOnlySpan<byte> _plainTextBody => "Hello, World!"u8;
private static ReadOnlySpan<byte> _contentLengthGap => " "u8;

private static readonly JsonContext SerializerContext = JsonContext.Default;
#if DATABASE
public static RawDb Db { get; set; }
#endif

[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(JsonMessage))]
[JsonSerializable(typeof(CachedWorld[]))]
[JsonSerializable(typeof(World[]))]
private sealed partial class JsonContext : JsonSerializerContext
private static readonly DefaultObjectPool<ChunkedBufferWriter<WriterAdapter>> ChunkedWriterPool
= new(new ChunkedWriterObjectPolicy());

private sealed class ChunkedWriterObjectPolicy : IPooledObjectPolicy<ChunkedBufferWriter<WriterAdapter>>
{
}
public ChunkedBufferWriter<WriterAdapter> Create() => new();

private static ReadOnlySpan<byte> _fortunesTableStart => "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>"u8;
private static ReadOnlySpan<byte> _fortunesRowStart => "<tr><td>"u8;
private static ReadOnlySpan<byte> _fortunesColumn => "</td><td>"u8;
private static ReadOnlySpan<byte> _fortunesRowEnd => "</td></tr>"u8;
private static ReadOnlySpan<byte> _fortunesTableEnd => "</table></body></html>"u8;
private static ReadOnlySpan<byte> _contentLengthGap => " "u8;
public bool Return(ChunkedBufferWriter<WriterAdapter> writer)
{
writer.Reset();
return true;
}
}

#if DATABASE
public static RawDb Db { get; set; }
#if NPGSQL
private readonly static SliceFactory<List<FortuneUtf8>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf8>>("/Templates/FortunesUtf8.cshtml");
#elif MYSQLCONNECTOR
private readonly static SliceFactory<List<FortuneUtf16>> FortunesTemplateFactory = RazorSlice.ResolveSliceFactory<List<FortuneUtf16>>("/Templates/FortunesUtf16.cshtml");
#else
#error "DATABASE defined by neither NPGSQL nor MYSQLCONNECTOR are defined"
#endif
#endif

[ThreadStatic]
private static Utf8JsonWriter t_writer;

private static readonly JsonContext SerializerContext = JsonContext.Default;

[JsonSourceGenerationOptions(GenerationMode = JsonSourceGenerationMode.Serialization, PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(JsonMessage))]
[JsonSerializable(typeof(CachedWorld[]))]
[JsonSerializable(typeof(World[]))]
private sealed partial class JsonContext : JsonSerializerContext
{
}

public static class Paths
{
public static ReadOnlySpan<byte> Json => "/json"u8;
Expand All @@ -78,41 +98,41 @@ public void OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathL
_requestType = versionAndMethod.Method == Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpMethod.Get ? GetRequestType(startLine.Slice(targetPath.Offset, targetPath.Length), ref _queries) : RequestType.NotRecognized;
}

private RequestType GetRequestType(ReadOnlySpan<byte> path, ref int queries)
private static RequestType GetRequestType(ReadOnlySpan<byte> path, ref int queries)
{
#if !DATABASE
if (path.Length == 10 && path.SequenceEqual(Paths.Plaintext))
{
return RequestType.PlainText;
}
else if (path.Length == 5 && path.SequenceEqual(Paths.Json))
if (path.Length == 5 && path.SequenceEqual(Paths.Json))
{
return RequestType.Json;
}
#else
if (path.Length == 3 && path[0] == '/' && path[1] == 'd' && path[2] == 'b')
{
return RequestType.SingleQuery;
}
else if (path.Length == 9 && path[1] == 'f' && path.SequenceEqual(Paths.Fortunes))
{
return RequestType.Fortunes;
}
else if (path.Length >= 15 && path[1] == 'c' && path.StartsWith(Paths.Caching))
{
queries = ParseQueries(path.Slice(15));
return RequestType.Caching;
}
else if (path.Length >= 9 && path[1] == 'u' && path.StartsWith(Paths.Updates))
{
queries = ParseQueries(path.Slice(9));
return RequestType.Updates;
}
else if (path.Length >= 9 && path[1] == 'q' && path.StartsWith(Paths.MultipleQueries))
{
queries = ParseQueries(path.Slice(9));
return RequestType.MultipleQueries;
}
if (path.Length == 3 && path[0] == '/' && path[1] == 'd' && path[2] == 'b')
{
return RequestType.SingleQuery;
}
if (path.Length == 9 && path[1] == 'f' && path.SequenceEqual(Paths.Fortunes))
{
return RequestType.Fortunes;
}
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;
}
Expand All @@ -138,13 +158,13 @@ private void ProcessRequest(ref BufferWriter<WriterAdapter> writer)

private static int ParseQueries(ReadOnlySpan<byte> parameter)
{
if (!Utf8Parser.TryParse(parameter, out int queries, out _) || queries < 1)
if (!Utf8Parser.TryParse(parameter, out int queries, out _))
{
queries = 1;
}
else if (queries > 500)
else
{
queries = 500;
queries = Math.Clamp(queries, 1, 500);
}

return queries;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static IWebHostBuilder UseBenchmarksConfiguration(this IWebHostBuilder bu

builder.UseSockets(options =>
{
if (int.TryParse(builder.GetSetting("threadCount"), out int threadCount))
if (int.TryParse(builder.GetSetting("threadCount"), out var threadCount))
{
options.IOQueueCount = threadCount;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void Advance(int count)
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> source)
public void Write(scoped ReadOnlySpan<byte> source)
{
if (_span.Length >= source.Length)
{
Expand Down Expand Up @@ -77,7 +77,7 @@ private void EnsureMore(int count = 0)
_span = _output.GetSpan(count);
}

private void WriteMultiBuffer(ReadOnlySpan<byte> source)
private void WriteMultiBuffer(scoped ReadOnlySpan<byte> source)
{
while (source.Length > 0)
{
Expand Down
Loading