Skip to content

Commit 64284c6

Browse files
Various performance improvements (#482)
- Call AdvanceTo in RequestBuffer only once - Remove ConfigureAwait() calls - Do not flush in pipelining mode
1 parent e95724b commit 64284c6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+106
-106
lines changed

Engine/Infrastructure/CoreRouter.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ internal CoreRouter(IHandlerBuilder content, IEnumerable<IConcernBuilder> concer
6868

6969
public async ValueTask PrepareAsync()
7070
{
71-
await Content.PrepareAsync().ConfigureAwait(false);
71+
await Content.PrepareAsync();
7272

7373
await Template.PrepareAsync();
7474

Engine/Infrastructure/Endpoints/EndPoint.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ protected EndPoint(IServer server, IPEndPoint endPoint, NetworkConfiguration con
6565
throw new BindingException($"Failed to bind to {endPoint}.", e);
6666
}
6767

68-
Task = Task.Run(() => Listen().ConfigureAwait(false));
68+
Task = Task.Run(() => Listen());
6969
}
7070

7171
#endregion
@@ -78,7 +78,7 @@ private async Task Listen()
7878
{
7979
do
8080
{
81-
Handle(await Socket.AcceptAsync().ConfigureAwait(false));
81+
Handle(await Socket.AcceptAsync());
8282
}
8383
while (!shuttingDown);
8484
}
@@ -95,14 +95,15 @@ private void Handle(Socket client)
9595
{
9696
using var _ = ExecutionContext.SuppressFlow();
9797

98-
Task.Run(() => Accept(client).ConfigureAwait(false))
99-
.ConfigureAwait(false);
98+
Task.Run(() => Accept(client));
10099
}
101100

102101
protected abstract PooledValueTask Accept(Socket client);
103102

104103
protected PooledValueTask Handle(Socket client, Stream inputStream)
105104
{
105+
client.NoDelay = true;
106+
106107
return new ClientHandler(client, inputStream, Server, this, Configuration).Run();
107108
}
108109

Engine/Infrastructure/Endpoints/SecureEndPoint.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ internal SecureEndPoint(IServer server, IPEndPoint endPoint, SecurityConfigurati
5454

5555
protected override async PooledValueTask Accept(Socket client)
5656
{
57-
var stream = await TryAuthenticate(client).ConfigureAwait(false);
57+
var stream = await TryAuthenticate(client);
5858

5959
if (stream is not null)
6060
{
61-
await Handle(client, new PoolBufferedStream(stream)).ConfigureAwait(false);
61+
await Handle(client, new PoolBufferedStream(stream));
6262
}
6363
else
6464
{
@@ -80,7 +80,7 @@ protected override async PooledValueTask Accept(Socket client)
8080
{
8181
var stream = new SslStream(new NetworkStream(client), false);
8282

83-
await stream.AuthenticateAsServerAsync(AuthenticationOptions, CancellationToken.None).ConfigureAwait(false);
83+
await stream.AuthenticateAsServerAsync(AuthenticationOptions, CancellationToken.None);
8484

8585
return stream;
8686
}

Engine/Protocol/ChunkedStream.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public override async Task WriteAsync(byte[] buffer, int offset, int count, Canc
8686
{
8787
if (count > 0)
8888
{
89-
await WriteAsync(count).ConfigureAwait(false);
89+
await WriteAsync(count);
9090
await WriteAsync(NL);
9191

9292
await Target.WriteAsync(buffer.AsMemory(offset, count), cancellationToken);
@@ -99,7 +99,7 @@ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, Cancella
9999
{
100100
if (!buffer.IsEmpty)
101101
{
102-
await WriteAsync(buffer.Length).ConfigureAwait(false);
102+
await WriteAsync(buffer.Length);
103103
await WriteAsync(NL);
104104

105105
await Target.WriteAsync(buffer, cancellationToken);
@@ -110,7 +110,7 @@ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, Cancella
110110

111111
public async ValueTask FinishAsync()
112112
{
113-
await WriteAsync("0").ConfigureAwait(false);
113+
await WriteAsync("0");
114114
await WriteAsync(NL);
115115

116116
await WriteAsync(NL);

Engine/Protocol/ClientHandler.cs

+7-7
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ internal ClientHandler(Socket socket, Stream stream, IServer server, IEndPoint e
5959

6060
Stream = stream;
6161

62-
ResponseHandler = new ResponseHandler(Server, Stream, Connection, Configuration);
62+
ResponseHandler = new ResponseHandler(Server, Stream, Configuration);
6363
}
6464

6565
#endregion
@@ -70,7 +70,7 @@ internal async PooledValueTask Run()
7070
{
7171
try
7272
{
73-
await HandlePipe(PipeReader.Create(Stream, READER_OPTIONS)).ConfigureAwait(false);
73+
await HandlePipe(PipeReader.Create(Stream, READER_OPTIONS));
7474
}
7575
catch (Exception e)
7676
{
@@ -115,9 +115,9 @@ private async PooledValueTask HandlePipe(PipeReader reader)
115115

116116
RequestBuilder? request;
117117

118-
while (Server.Running && (request = await parser.TryParseAsync(buffer).ConfigureAwait(false)) is not null)
118+
while (Server.Running && (request = await parser.TryParseAsync(buffer)) is not null)
119119
{
120-
if (!await HandleRequest(request))
120+
if (!await HandleRequest(request, !buffer.ReadRequired))
121121
{
122122
break;
123123
}
@@ -129,7 +129,7 @@ private async PooledValueTask HandlePipe(PipeReader reader)
129129
}
130130
}
131131

132-
private async PooledValueTask<bool> HandleRequest(RequestBuilder builder)
132+
private async PooledValueTask<bool> HandleRequest(RequestBuilder builder, bool dataRemaining)
133133
{
134134
var address = (Connection.RemoteEndPoint as IPEndPoint)?.Address;
135135

@@ -139,9 +139,9 @@ private async PooledValueTask<bool> HandleRequest(RequestBuilder builder)
139139

140140
bool keepAlive = (bool)KeepAlive;
141141

142-
using var response = await Server.Handler.HandleAsync(request).ConfigureAwait(false) ?? throw new InvalidOperationException("The root request handler did not return a response");
142+
using var response = await Server.Handler.HandleAsync(request) ?? throw new InvalidOperationException("The root request handler did not return a response");
143143

144-
var success = await ResponseHandler.Handle(request, response, keepAlive);
144+
var success = await ResponseHandler.Handle(request, response, keepAlive, dataRemaining);
145145

146146
if (!success || !keepAlive)
147147
{

Engine/Protocol/Parser/RequestScanner.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ private static async PooledValueTask<bool> Fill(RequestBuffer buffer, bool force
147147
{
148148
if (buffer.ReadRequired || force)
149149
{
150-
await buffer.Read(force).ConfigureAwait(false);
150+
await buffer.Read(force);
151151
}
152152

153153
return !buffer.Data.IsEmpty;

Engine/Protocol/RequestBuffer.cs

+12-10
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ namespace GenHTTP.Engine.Protocol
2525
/// </remarks>
2626
internal sealed class RequestBuffer : IDisposable
2727
{
28+
private ReadOnlySequence<byte>? _Data;
2829

2930
#region Get-/Setters
3031

@@ -34,9 +35,9 @@ internal sealed class RequestBuffer : IDisposable
3435

3536
private CancellationTokenSource? Cancellation { get; set; }
3637

37-
internal ReadOnlySequence<byte> Data { get; private set; }
38+
internal ReadOnlySequence<byte> Data => _Data ?? new();
3839

39-
internal bool ReadRequired => Data.IsEmpty;
40+
internal bool ReadRequired => (_Data == null) || _Data.Value.IsEmpty;
4041

4142
#endregion
4243

@@ -46,8 +47,6 @@ internal RequestBuffer(PipeReader reader, NetworkConfiguration configuration)
4647
{
4748
Reader = reader;
4849
Configuration = configuration;
49-
50-
Data = new();
5150
}
5251

5352
#endregion
@@ -56,8 +55,13 @@ internal RequestBuffer(PipeReader reader, NetworkConfiguration configuration)
5655

5756
internal async PooledValueTask<long?> Read(bool force = false)
5857
{
59-
if ((Data.Length == 0) || force)
58+
if (ReadRequired || force)
6059
{
60+
if (_Data != null)
61+
{
62+
Reader.AdvanceTo(_Data.Value.Start);
63+
}
64+
6165
if (Cancellation is null)
6266
{
6367
Cancellation = new();
@@ -67,7 +71,7 @@ internal RequestBuffer(PipeReader reader, NetworkConfiguration configuration)
6771
{
6872
Cancellation.CancelAfter(Configuration.RequestReadTimeout);
6973

70-
Data = (await Reader.ReadAsync(Cancellation.Token).ConfigureAwait(false)).Buffer;
74+
_Data = (await Reader.ReadAsync(Cancellation.Token)).Buffer;
7175

7276
Cancellation.CancelAfter(int.MaxValue);
7377
}
@@ -85,14 +89,12 @@ internal RequestBuffer(PipeReader reader, NetworkConfiguration configuration)
8589

8690
internal void Advance(SequencePosition position)
8791
{
88-
Data = Data.Slice(position);
89-
Reader.AdvanceTo(Data.Start);
92+
_Data = Data.Slice(position);
9093
}
9194

9295
internal void Advance(long bytes)
9396
{
94-
Data = Data.Slice(bytes);
95-
Reader.AdvanceTo(Data.Start);
97+
_Data = Data.Slice(bytes);
9698
}
9799

98100
#endregion

Engine/Protocol/RequestContentParser.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ internal async Task<Stream> GetBody(RequestBuffer buffer)
4242

4343
while (toFetch > 0)
4444
{
45-
await buffer.Read().ConfigureAwait(false);
45+
await buffer.Read();
4646

4747
var toRead = Math.Min(buffer.Data.Length, Math.Min(Configuration.TransferBufferSize, toFetch));
4848

Engine/Protocol/ResponseHandler.cs

+15-18
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Buffers;
33
using System.IO;
4-
using System.Net.Sockets;
54
using System.Text;
65
using System.Threading.Tasks;
76

@@ -30,20 +29,17 @@ internal sealed class ResponseHandler
3029

3130
private Stream OutputStream { get; }
3231

33-
private Socket Socket { get; }
34-
3532
internal NetworkConfiguration Configuration { get; }
3633

3734
#endregion
3835

3936
#region Initialization
4037

41-
internal ResponseHandler(IServer server, Stream outputstream, Socket socket, NetworkConfiguration configuration)
38+
internal ResponseHandler(IServer server, Stream outputstream, NetworkConfiguration configuration)
4239
{
4340
Server = server;
4441

4542
OutputStream = outputstream;
46-
Socket = socket;
4743

4844
Configuration = configuration;
4945
}
@@ -52,11 +48,11 @@ internal ResponseHandler(IServer server, Stream outputstream, Socket socket, Net
5248

5349
#region Functionality
5450

55-
internal async ValueTask<bool> Handle(IRequest request, IResponse response, bool keepAlive)
51+
internal async ValueTask<bool> Handle(IRequest request, IResponse response, bool keepAlive, bool dataRemaining)
5652
{
5753
try
5854
{
59-
await WriteStatus(request, response).ConfigureAwait(false);
55+
await WriteStatus(request, response);
6056

6157
await WriteHeader(response, keepAlive);
6258

@@ -67,11 +63,12 @@ internal async ValueTask<bool> Handle(IRequest request, IResponse response, bool
6763
await WriteBody(response);
6864
}
6965

70-
Socket.NoDelay = true;
71-
72-
await OutputStream.FlushAsync();
73-
74-
Socket.NoDelay = false;
66+
// flush if the client waits for this response
67+
// otherwise save flushes for improved performance when pipelining
68+
if (!dataRemaining)
69+
{
70+
await OutputStream.FlushAsync();
71+
}
7572

7673
Server.Companion?.OnRequestHandled(request, response);
7774

@@ -104,11 +101,11 @@ private async ValueTask WriteHeader(IResponse response, bool keepAlive)
104101
{
105102
if (response.Headers.TryGetValue(SERVER_HEADER, out var server))
106103
{
107-
await WriteHeaderLine(SERVER_HEADER, server).ConfigureAwait(false);
104+
await WriteHeaderLine(SERVER_HEADER, server);
108105
}
109106
else
110107
{
111-
await Write("Server: GenHTTP/", Server.Version, NL).ConfigureAwait(false);
108+
await Write("Server: GenHTTP/", Server.Version, NL);
112109
}
113110

114111
await WriteHeaderLine("Date", DateHeader.GetValue());
@@ -183,13 +180,13 @@ private async ValueTask WriteBody(IResponse response)
183180
{
184181
using var chunked = new ChunkedStream(OutputStream);
185182

186-
await response.Content.WriteAsync(chunked, Configuration.TransferBufferSize).ConfigureAwait(false);
183+
await response.Content.WriteAsync(chunked, Configuration.TransferBufferSize);
187184

188185
await chunked.FinishAsync();
189186
}
190187
else
191188
{
192-
await response.Content.WriteAsync(OutputStream, Configuration.TransferBufferSize).ConfigureAwait(false);
189+
await response.Content.WriteAsync(OutputStream, Configuration.TransferBufferSize);
193190
}
194191
}
195192
}
@@ -204,7 +201,7 @@ private async ValueTask WriteBody(IResponse response)
204201

205202
private async ValueTask WriteCookie(Cookie cookie)
206203
{
207-
await Write("Set-Cookie: ", cookie.Name, "=", cookie.Value).ConfigureAwait(false);
204+
await Write("Set-Cookie: ", cookie.Name, "=", cookie.Value);
208205

209206
if (cookie.MaxAge is not null)
210207
{
@@ -264,7 +261,7 @@ private async ValueTask Write(string part1, string? part2 = null, string? part3
264261
ASCII.GetBytes(part7, 0, part7.Length, buffer, index);
265262
}
266263

267-
await OutputStream.WriteAsync(buffer.AsMemory(0, length)).ConfigureAwait(false);
264+
await OutputStream.WriteAsync(buffer.AsMemory(0, length));
268265
}
269266
finally
270267
{

Engine/Utilities/PoolBufferedStream.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public PoolBufferedStream(Stream stream)
5555
{
5656
Stream = stream;
5757

58-
Buffer = POOL.Rent(8192);
58+
Buffer = POOL.Rent(4096);
5959
Current = 0;
6060
}
6161

@@ -115,12 +115,12 @@ public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, Cancella
115115

116116
if (Current == Buffer.Length)
117117
{
118-
await WriteBufferAsync(cancellationToken).ConfigureAwait(false);
118+
await WriteBufferAsync(cancellationToken);
119119
}
120120
}
121121
else
122122
{
123-
await WriteBufferAsync(cancellationToken).ConfigureAwait(false);
123+
await WriteBufferAsync(cancellationToken);
124124

125125
await Stream.WriteAsync(buffer, cancellationToken);
126126
}
@@ -135,7 +135,7 @@ public override void Flush()
135135

136136
public override async Task FlushAsync(CancellationToken cancellationToken)
137137
{
138-
await WriteBufferAsync(cancellationToken).ConfigureAwait(false);
138+
await WriteBufferAsync(cancellationToken);
139139

140140
await Stream.FlushAsync(cancellationToken);
141141
}
@@ -154,7 +154,7 @@ private async ValueTask WriteBufferAsync(CancellationToken cancellationToken)
154154
{
155155
if (Current > 0)
156156
{
157-
await Stream.WriteAsync(Buffer.AsMemory(0, Current), cancellationToken).ConfigureAwait(false);
157+
await Stream.WriteAsync(Buffer.AsMemory(0, Current), cancellationToken);
158158

159159
Current = 0;
160160
}

Modules/Authentication.Web/Concern/WebAuthenticationConcern.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ public WebAuthenticationConcern(IHandler parent, Func<IHandler, IHandler> conten
6767
{
6868
request.Target.Advance();
6969

70-
return await ResourceHandler.HandleAsync(request).ConfigureAwait(false);
70+
return await ResourceHandler.HandleAsync(request);
7171
}
7272

73-
if (await Integration.CheckSetupRequired(request).ConfigureAwait(false))
73+
if (await Integration.CheckSetupRequired(request))
7474
{
7575
if (segment?.Value != Integration.SetupRoute)
7676
{

0 commit comments

Comments
 (0)