Merge pull request #699 from aspnet/release/2.1

This commit is contained in:
Justin Kotalik 2018-03-19 17:10:44 -07:00 committed by GitHub
commit be08942b57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 30 deletions

View File

@ -27,7 +27,6 @@
<attribute name="disableStartUpErrorPage" type="bool" defaultValue="false" /> <attribute name="disableStartUpErrorPage" type="bool" defaultValue="false" />
<attribute name="hostingModel" type="string" /> <attribute name="hostingModel" type="string" />
<element name="recycleOnFileChange"> <element name="recycleOnFileChange">
<collection addElement="file" clearElement="clear"> <collection addElement="file" clearElement="clear">
<attribute name="path" type="string" required="true" validationType="nonEmptyString" expanded="true"/> <attribute name="path" type="string" required="true" validationType="nonEmptyString" expanded="true"/>
</collection> </collection>
@ -38,5 +37,11 @@
<attribute name="value" type="string" required="true"/> <attribute name="value" type="string" required="true"/>
</collection> </collection>
</element> </element>
<element name="handlerSettings">
<collection addElement="handlerSetting" clearElement="clear" >
<attribute name="name" type="string" required="true" validationType="nonEmptyString"/>
<attribute name="value" type="string" required="true"/>
</collection>
</element>
</sectionSchema> </sectionSchema>
</configSchema> </configSchema>

View File

@ -12,6 +12,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
{ {
internal partial class IISHttpContext internal partial class IISHttpContext
{ {
private const int HttpDataChunkStackLimit = 128; // 16 bytes per HTTP_DATA_CHUNK
/// <summary> /// <summary>
/// Reads data from the Input pipe to the user. /// Reads data from the Input pipe to the user.
/// </summary> /// </summary>
@ -212,6 +214,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
var hr = 0; var hr = 0;
var nChunks = 0; var nChunks = 0;
// Count the number of chunks in memory.
if (buffer.IsSingleSegment) if (buffer.IsSingleSegment)
{ {
nChunks = 1; nChunks = 1;
@ -224,8 +227,9 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
} }
} }
if (buffer.IsSingleSegment) if (nChunks == 1)
{ {
// If there is only a single chunk, use fixed to get a pointer to the buffer
var pDataChunks = stackalloc HttpApiTypes.HTTP_DATA_CHUNK[1]; var pDataChunks = stackalloc HttpApiTypes.HTTP_DATA_CHUNK[1];
fixed (byte* pBuffer = &MemoryMarshal.GetReference(buffer.First.Span)) fixed (byte* pBuffer = &MemoryMarshal.GetReference(buffer.First.Span))
@ -238,35 +242,20 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
hr = NativeMethods.http_write_response_bytes(_pInProcessHandler, pDataChunks, nChunks, out fCompletionExpected); hr = NativeMethods.http_write_response_bytes(_pInProcessHandler, pDataChunks, nChunks, out fCompletionExpected);
} }
} }
else if (nChunks < HttpDataChunkStackLimit)
{
// To avoid stackoverflows, we will only stackalloc if the write size is less than the StackChunkLimit
// The stack size is IIS is by default 128/256 KB, so we are generous with this threshold.
var pDataChunks = stackalloc HttpApiTypes.HTTP_DATA_CHUNK[nChunks];
hr = WriteSequenceToIIS(nChunks, buffer, pDataChunks, out fCompletionExpected);
}
else else
{ {
// REVIEW: Do we need to guard against this getting too big? It seems unlikely that we'd have more than say 10 chunks in real life // Otherwise allocate the chunks on the heap.
var pDataChunks = stackalloc HttpApiTypes.HTTP_DATA_CHUNK[nChunks]; var chunks = new HttpApiTypes.HTTP_DATA_CHUNK[nChunks];
var currentChunk = 0; fixed (HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks = chunks)
// REVIEW: We don't really need this list since the memory is already pinned with the default pool,
// but shouldn't assume the pool implementation right now. Unfortunately, this causes a heap allocation...
var handles = new MemoryHandle[nChunks];
foreach (var b in buffer)
{ {
ref var handle = ref handles[currentChunk]; hr = WriteSequenceToIIS(nChunks, buffer, pDataChunks, out fCompletionExpected);
ref var chunk = ref pDataChunks[currentChunk];
handle = b.Retain(true);
chunk.DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
chunk.fromMemory.BufferLength = (uint)b.Length;
chunk.fromMemory.pBuffer = (IntPtr)handle.Pointer;
currentChunk++;
}
hr = NativeMethods.http_write_response_bytes(_pInProcessHandler, pDataChunks, nChunks, out fCompletionExpected);
// Free the handles
foreach (var handle in handles)
{
handle.Dispose();
} }
} }
@ -277,6 +266,39 @@ namespace Microsoft.AspNetCore.Server.IISIntegration
return _operation; return _operation;
} }
private unsafe int WriteSequenceToIIS(int nChunks, ReadOnlySequence<byte> buffer, HttpApiTypes.HTTP_DATA_CHUNK* pDataChunks, out bool fCompletionExpected)
{
var currentChunk = 0;
var hr = 0;
// REVIEW: We don't really need this list since the memory is already pinned with the default pool,
// but shouldn't assume the pool implementation right now. Unfortunately, this causes a heap allocation...
var handles = new MemoryHandle[nChunks];
foreach (var b in buffer)
{
ref var handle = ref handles[currentChunk];
ref var chunk = ref pDataChunks[currentChunk];
handle = b.Retain(true);
chunk.DataChunkType = HttpApiTypes.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
chunk.fromMemory.BufferLength = (uint)b.Length;
chunk.fromMemory.pBuffer = (IntPtr)handle.Pointer;
currentChunk++;
}
hr = NativeMethods.http_write_response_bytes(_pInProcessHandler, pDataChunks, nChunks, out fCompletionExpected);
// Free the handles
foreach (var handle in handles)
{
handle.Dispose();
}
return hr;
}
private unsafe IISAwaitable FlushToIISAsync() private unsafe IISAwaitable FlushToIISAsync()
{ {
// Calls flush // Calls flush

View File

@ -18,11 +18,19 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
} }
[ConditionalTheory] [ConditionalTheory]
[InlineData(65000)]
[InlineData(1000000)] [InlineData(1000000)]
[InlineData(10000000)] [InlineData(10000000)]
public async Task LargeResponseBodyTestCheckAllResponseBodyBytesWritten(int query) [InlineData(100000000)]
public async Task LargeResponseBodyTest_CheckAllResponseBodyBytesWritten(int query)
{ {
Assert.Equal(new string('a', query), await _fixture.Client.GetStringAsync($"/LargeResponseBody?length={query}")); Assert.Equal(new string('a', query), await _fixture.Client.GetStringAsync($"/LargeResponseBody?length={query}"));
} }
[ConditionalFact]
public async Task LargeResponseBodyFromFile_CheckAllResponseBodyBytesWritten()
{
Assert.Equal(200000000, (await _fixture.Client.GetStringAsync($"/LargeResponseFile")).Length);
}
} }
} }

View File

@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal(body, responseText); Assert.Equal(body, responseText);
} }
[ConditionalFact] [ConditionalFact(Skip = "See https://github.com/aspnet/IISIntegration/issues/687")]
public async Task ReadAndWriteEchoTwice() public async Task ReadAndWriteEchoTwice()
{ {
var requestBody = new string('a', 10000); var requestBody = new string('a', 10000);

View File

@ -49,6 +49,7 @@ namespace IISTestSite
app.Map("/TestInvalidReadOperations", TestInvalidReadOperations); app.Map("/TestInvalidReadOperations", TestInvalidReadOperations);
app.Map("/TestInvalidWriteOperations", TestInvalidWriteOperations); app.Map("/TestInvalidWriteOperations", TestInvalidWriteOperations);
app.Map("/TestReadOffsetWorks", TestReadOffsetWorks); app.Map("/TestReadOffsetWorks", TestReadOffsetWorks);
app.Map("/LargeResponseFile", LargeResponseFile);
} }
private void ServerVariable(IApplicationBuilder app) private void ServerVariable(IApplicationBuilder app)
@ -628,5 +629,26 @@ namespace IISTestSite
await context.Response.WriteAsync(success ? "Success" : "Failure"); await context.Response.WriteAsync(success ? "Success" : "Failure");
}); });
} }
private void LargeResponseFile(IApplicationBuilder app)
{
app.Run(async ctx =>
{
var tempFile = Path.GetTempFileName();
File.WriteAllText(tempFile, new string('a', 200000000));
await ctx.Response.SendFileAsync(tempFile, 0, null);
// Try to delete the file from the temp directory. If it fails, don't report an error
// to the application. File should eventually be cleaned up from the temp directory
// by OS.
try
{
File.Delete(tempFile);
}
catch (Exception)
{
}
});
}
} }
} }