Remove over engineered callback writing and just write chunks directly. (#6901)

This commit is contained in:
David Fowler 2019-01-28 20:36:47 +00:00 committed by GitHub
parent 7d4b6fccff
commit 549f8e1773
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 37 additions and 51 deletions

View File

@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return WriteAsync(Constants.EmptyData, cancellationToken); return WriteAsync(Constants.EmptyData, cancellationToken);
} }
public Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state, CancellationToken cancellationToken) public Task WriteChunkAsync(ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
{ {
lock (_contextLock) lock (_contextLock)
{ {
@ -80,9 +80,17 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return Task.CompletedTask; return Task.CompletedTask;
} }
var buffer = _pipeWriter; if (buffer.Length > 0)
var bytesCommitted = callback(buffer, state); {
_unflushedBytes += bytesCommitted; var writer = new BufferWriter<PipeWriter>(_pipeWriter);
writer.WriteBeginChunkBytes(buffer.Length);
writer.Write(buffer);
writer.WriteEndChunkBytes();
writer.Commit();
_unflushedBytes += writer.BytesCommitted;
}
} }
return FlushAsync(cancellationToken); return FlushAsync(cancellationToken);

View File

@ -30,7 +30,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive"); private static readonly byte[] _bytesConnectionKeepAlive = Encoding.ASCII.GetBytes("\r\nConnection: keep-alive");
private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked"); private static readonly byte[] _bytesTransferEncodingChunked = Encoding.ASCII.GetBytes("\r\nTransfer-Encoding: chunked");
private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: " + Constants.ServerName); private static readonly byte[] _bytesServer = Encoding.ASCII.GetBytes("\r\nServer: " + Constants.ServerName);
private static readonly Func<PipeWriter, ReadOnlyMemory<byte>, long> _writeChunk = WriteChunk;
private readonly object _onStartingSync = new Object(); private readonly object _onStartingSync = new Object();
private readonly object _onCompletedSync = new Object(); private readonly object _onCompletedSync = new Object();
@ -820,7 +819,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
return !firstWrite ? Task.CompletedTask : FlushAsync(cancellationToken); return !firstWrite ? Task.CompletedTask : FlushAsync(cancellationToken);
} }
return WriteChunkedAsync(data, cancellationToken); return WriteChunkedAsync(data.Span, cancellationToken);
} }
else else
{ {
@ -851,7 +850,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
return; return;
} }
await WriteChunkedAsync(data, cancellationToken); await WriteChunkedAsync(data.Span, cancellationToken);
} }
else else
{ {
@ -936,27 +935,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
} }
private Task WriteChunkedAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken) private Task WriteChunkedAsync(ReadOnlySpan<byte> data, CancellationToken cancellationToken)
{ {
return Output.WriteAsync(_writeChunk, data, cancellationToken); return Output.WriteChunkAsync(data, cancellationToken);
}
private static long WriteChunk(PipeWriter writableBuffer, ReadOnlyMemory<byte> buffer)
{
var bytesWritten = 0L;
if (buffer.Length > 0)
{
var writer = new BufferWriter<PipeWriter>(writableBuffer);
writer.WriteBeginChunkBytes(buffer.Length);
writer.Write(buffer.Span);
writer.WriteEndChunkBytes();
writer.Commit();
bytesWritten = writer.BytesCommitted;
}
return bytesWritten;
} }
public void ProduceContinue() public void ProduceContinue()

View File

@ -2,7 +2,6 @@
// 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.
using System; using System;
using System.IO.Pipelines;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,7 +9,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
public interface IHttpOutputProducer public interface IHttpOutputProducer
{ {
Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state, CancellationToken cancellationToken); Task WriteChunkAsync(ReadOnlySpan<byte> data, CancellationToken cancellationToken);
Task FlushAsync(CancellationToken cancellationToken); Task FlushAsync(CancellationToken cancellationToken);
Task Write100ContinueAsync(); Task Write100ContinueAsync();
void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders); void WriteResponseHeaders(int statusCode, string ReasonPhrase, HttpResponseHeaders responseHeaders);

View File

@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
Dispose(); Dispose();
} }
public Task WriteAsync<T>(Func<PipeWriter, T, long> callback, T state, CancellationToken cancellationToken) public Task WriteChunkAsync(ReadOnlySpan<byte> span, CancellationToken cancellationToken)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@ -1,4 +1,4 @@
// 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.
using System; using System;
@ -46,17 +46,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
// Close // Close
socketOutput.Dispose(); socketOutput.Dispose();
var called = false; await socketOutput.WriteDataAsync(new byte[] { 1, 2, 3, 4 }, default);
await socketOutput.WriteAsync((buffer, state) => Assert.True(socketOutput.Pipe.Reader.TryRead(out var result));
{ Assert.True(result.IsCompleted);
called = true; Assert.True(result.Buffer.IsEmpty);
return 0;
},
0,
default);
Assert.False(called);
} }
} }
@ -80,7 +74,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
mockConnectionContext.Verify(f => f.Abort(null), Times.Once()); mockConnectionContext.Verify(f => f.Abort(null), Times.Once());
} }
private Http1OutputProducer CreateOutputProducer( private TestHttpOutputProducer CreateOutputProducer(
PipeOptions pipeOptions = null, PipeOptions pipeOptions = null,
ConnectionContext connectionContext = null) ConnectionContext connectionContext = null)
{ {
@ -89,8 +83,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
var pipe = new Pipe(pipeOptions); var pipe = new Pipe(pipeOptions);
var serviceContext = new TestServiceContext(); var serviceContext = new TestServiceContext();
var socketOutput = new Http1OutputProducer( var socketOutput = new TestHttpOutputProducer(
pipe.Writer, pipe,
"0", "0",
connectionContext, connectionContext,
serviceContext.Log, serviceContext.Log,
@ -99,5 +93,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
return socketOutput; return socketOutput;
} }
private class TestHttpOutputProducer : Http1OutputProducer
{
public TestHttpOutputProducer(Pipe pipe, string connectionId, ConnectionContext connectionContext, IKestrelTrace log, ITimeoutControl timeoutControl, IHttpMinResponseDataRateFeature minResponseDataRateFeature) : base(pipe.Writer, connectionId, connectionContext, log, timeoutControl, minResponseDataRateFeature)
{
Pipe = pipe;
}
public Pipe Pipe { get; }
}
} }
} }

View File

@ -304,13 +304,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Tests
Assert.NotEmpty(completeQueue); Assert.NotEmpty(completeQueue);
// Add more bytes to the write-behind buffer to prevent the next write from // Add more bytes to the write-behind buffer to prevent the next write from
_ = outputProducer.WriteAsync((writableBuffer, state) => _ = outputProducer.WriteDataAsync(halfWriteBehindBuffer, default);
{
writableBuffer.Write(state);
return state.Count;
},
halfWriteBehindBuffer,
default);
// Act // Act
var writeTask2 = outputProducer.WriteDataAsync(halfWriteBehindBuffer); var writeTask2 = outputProducer.WriteDataAsync(halfWriteBehindBuffer);