Skip to content
Open
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 @@ -668,6 +668,13 @@ private void WriteCurrentChunkMemoryToPipeWriter(ref BufferWriter<PipeWriter> wr

internal Memory<byte> GetFakeMemory(int minSize)
{
if (minSize == 0)
{
// MemoryPool.Rent(0) may return empty memory;
// use Kestrel's minimum segment size to satisfy the IBufferWriter contract.
minSize = _memoryPool.GetMinimumSegmentSize();
}

// Try to reuse _fakeMemoryOwner
if (_fakeMemoryOwner != null)
{
Expand Down Expand Up @@ -735,6 +742,13 @@ private void EnsureCapacity(int sizeHint)

private void AddSegment(int sizeHint = 0)
{
if (sizeHint == 0)
{
// MemoryPool.Rent(0) may return empty memory;
// use Kestrel's minimum segment size to satisfy the IBufferWriter contract.
sizeHint = _memoryPool.GetMinimumSegmentSize();
}

if (_currentSegment.Length != 0)
{
// We're adding a segment to the list
Expand Down
34 changes: 32 additions & 2 deletions src/Servers/Kestrel/Core/test/Http1/Http1OutputProducerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,35 @@ public void AbortsTransportEvenAfterDispose()
Assert.Equal(ConnectionEndReason.AbortedByApp, metricsContext.ConnectionEndReason);
}

[Fact]
public void GetMemoryAndGetSpanWithZeroSizeHintReturnNonEmptyBuffers()
{
var memoryPool = new Mock<MemoryPool<byte>>();
memoryPool.SetupGet(pool => pool.MaxBufferSize).Returns(MemoryPool<byte>.Shared.MaxBufferSize);
memoryPool.Setup(pool => pool.Rent(0)).Returns(Mock.Of<IMemoryOwner<byte>>());
memoryPool.Setup(pool => pool.Rent(It.Is<int>(size => size > 0)))
.Returns((int size) => MemoryPool<byte>.Shared.Rent(size));

using var output = CreateOutputProducer(memoryPool: memoryPool.Object);

var beforeStartMemoryLength = output.GetMemory(0).Length;
var beforeStartSpanLength = output.GetSpan(0).Length;

output.Stop();

var completedMemoryLength = output.GetMemory(0).Length;
var completedSpanLength = output.GetSpan(0).Length;

Assert.All(
[
(Operation: "GetMemory before response start", Length: beforeStartMemoryLength),
(Operation: "GetSpan before response start", Length: beforeStartSpanLength),
(Operation: "GetMemory after completion", Length: completedMemoryLength),
(Operation: "GetSpan after completion", Length: completedSpanLength),
],
result => Assert.True(result.Length > 0, $"{result.Operation} returned an empty buffer."));
}

[Fact]
public void AllocatesFakeMemorySmallerThanMaxBufferSize()
{
Expand Down Expand Up @@ -223,7 +252,8 @@ public void ReusesFakeMemory()
private TestHttpOutputProducer CreateOutputProducer(
PipeOptions pipeOptions = null,
ConnectionContext connectionContext = null,
ConnectionMetricsContext metricsContext = null)
ConnectionMetricsContext metricsContext = null,
MemoryPool<byte> memoryPool = null)
{
pipeOptions = pipeOptions ?? new PipeOptions();
connectionContext = connectionContext ?? Mock.Of<ConnectionContext>();
Expand All @@ -234,7 +264,7 @@ private TestHttpOutputProducer CreateOutputProducer(
pipe,
"0",
connectionContext,
_memoryPool,
memoryPool ?? _memoryPool,
serviceContext.Log,
Mock.Of<ITimeoutControl>(),
Mock.Of<IHttpMinResponseDataRateFeature>(),
Expand Down
Loading