Skip to content

Commit c848c33

Browse files
alefranzJohn Luo
authored andcommitted
ResponseCaching: started conversion to pipes (#16961)
* ResponseCaching: started conversion to pipes * nits * Use span instead of memory * CachedResponseBody Tests * Benchmark * Reworked benchmark * Addressed feedback * Increased timeout
1 parent 44e4493 commit c848c33

14 files changed

+368
-533
lines changed

src/Middleware/Middleware.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebSoc
297297
EndProject
298298
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Perf", "Perf", "{4623F52E-2070-4631-8DEE-7D2F48733FFD}"
299299
EndProject
300+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.ResponseCaching.Microbenchmarks", "perf\ResponseCaching.Microbenchmarks\Microsoft.AspNetCore.ResponseCaching.Microbenchmarks.csproj", "{80C8E810-1206-482E-BE17-961DD2EBFB11}"
301+
EndProject
300302
Global
301303
GlobalSection(SolutionConfigurationPlatforms) = preSolution
302304
Debug|Any CPU = Debug|Any CPU
@@ -1615,6 +1617,18 @@ Global
16151617
{C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x64.Build.0 = Release|Any CPU
16161618
{C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x86.ActiveCfg = Release|Any CPU
16171619
{C4D624B3-749E-41D8-A43B-B304BC3885EA}.Release|x86.Build.0 = Release|Any CPU
1620+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1621+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Debug|Any CPU.Build.0 = Debug|Any CPU
1622+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Debug|x64.ActiveCfg = Debug|Any CPU
1623+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Debug|x64.Build.0 = Debug|Any CPU
1624+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Debug|x86.ActiveCfg = Debug|Any CPU
1625+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Debug|x86.Build.0 = Debug|Any CPU
1626+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Release|Any CPU.ActiveCfg = Release|Any CPU
1627+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Release|Any CPU.Build.0 = Release|Any CPU
1628+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Release|x64.ActiveCfg = Release|Any CPU
1629+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Release|x64.Build.0 = Release|Any CPU
1630+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Release|x86.ActiveCfg = Release|Any CPU
1631+
{80C8E810-1206-482E-BE17-961DD2EBFB11}.Release|x86.Build.0 = Release|Any CPU
16181632
EndGlobalSection
16191633
GlobalSection(SolutionProperties) = preSolution
16201634
HideSolutionNode = FALSE
@@ -1742,6 +1756,7 @@ Global
17421756
{92E11EBB-759E-4DA8-AB61-A9977D9F97D0} = {ACA6DDB9-7592-47CE-A740-D15BF307E9E0}
17431757
{D0CB733B-4CE8-4F6C-BBB9-548EA1A96966} = {D6FA4ABE-E685-4EDD-8B06-D8777E76B472}
17441758
{C4D624B3-749E-41D8-A43B-B304BC3885EA} = {4623F52E-2070-4631-8DEE-7D2F48733FFD}
1759+
{80C8E810-1206-482E-BE17-961DD2EBFB11} = {4623F52E-2070-4631-8DEE-7D2F48733FFD}
17451760
EndGlobalSection
17461761
GlobalSection(ExtensibilityGlobals) = postSolution
17471762
SolutionGuid = {83786312-A93B-4BB4-AB06-7C6913A59AFA}

src/Middleware/ResponseCaching/src/CacheEntry/CachedResponse.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5-
using System.IO;
65
using Microsoft.AspNetCore.Http;
76

87
namespace Microsoft.AspNetCore.ResponseCaching
@@ -15,6 +14,6 @@ internal class CachedResponse : IResponseCacheEntry
1514

1615
public IHeaderDictionary Headers { get; set; }
1716

18-
public Stream Body { get; set; }
17+
public CachedResponseBody Body { get; set; }
1918
}
2019
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO.Pipelines;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace Microsoft.AspNetCore.ResponseCaching
11+
{
12+
internal class CachedResponseBody
13+
{
14+
public CachedResponseBody(List<byte[]> segments, long length)
15+
{
16+
Segments = segments;
17+
Length = length;
18+
}
19+
20+
public List<byte[]> Segments { get; }
21+
22+
public long Length { get; }
23+
24+
public async Task CopyToAsync(PipeWriter destination, CancellationToken cancellationToken)
25+
{
26+
if (destination == null)
27+
{
28+
throw new ArgumentNullException(nameof(destination));
29+
}
30+
31+
foreach (var segment in Segments)
32+
{
33+
cancellationToken.ThrowIfCancellationRequested();
34+
35+
Copy(segment, destination);
36+
37+
await destination.FlushAsync();
38+
}
39+
}
40+
41+
private static void Copy(byte[] segment, PipeWriter destination)
42+
{
43+
var span = destination.GetSpan(segment.Length);
44+
45+
segment.CopyTo(span);
46+
destination.Advance(segment.Length);
47+
}
48+
}
49+
}

src/Middleware/ResponseCaching/src/MemoryCachedResponse.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
@@ -15,8 +15,6 @@ internal class MemoryCachedResponse
1515

1616
public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
1717

18-
public List<byte[]> BodySegments { get; set; }
19-
20-
public long BodyLength { get; set; }
18+
public CachedResponseBody Body { get; set; }
2119
}
2220
}

src/Middleware/ResponseCaching/src/MemoryResponseCache.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Buffers;
56
using System.Threading.Tasks;
67
using Microsoft.Extensions.Caching.Memory;
78

@@ -27,7 +28,7 @@ public IResponseCacheEntry Get(string key)
2728
Created = memoryCachedResponse.Created,
2829
StatusCode = memoryCachedResponse.StatusCode,
2930
Headers = memoryCachedResponse.Headers,
30-
Body = new SegmentReadStream(memoryCachedResponse.BodySegments, memoryCachedResponse.BodyLength)
31+
Body = memoryCachedResponse.Body
3132
};
3233
}
3334
else
@@ -40,18 +41,14 @@ public void Set(string key, IResponseCacheEntry entry, TimeSpan validFor)
4041
{
4142
if (entry is CachedResponse cachedResponse)
4243
{
43-
var segmentStream = new SegmentWriteStream(StreamUtilities.BodySegmentSize);
44-
cachedResponse.Body.CopyTo(segmentStream);
45-
4644
_cache.Set(
4745
key,
4846
new MemoryCachedResponse
4947
{
5048
Created = cachedResponse.Created,
5149
StatusCode = cachedResponse.StatusCode,
5250
Headers = cachedResponse.Headers,
53-
BodySegments = segmentStream.GetSegments(),
54-
BodyLength = segmentStream.Length
51+
Body = cachedResponse.Body
5552
},
5653
new MemoryCacheEntryOptions
5754
{

src/Middleware/ResponseCaching/src/ResponseCachingMiddleware.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ internal async Task<bool> TryServeCachedResponseAsync(ResponseCachingContext con
192192
{
193193
try
194194
{
195-
await body.CopyToAsync(response.Body, StreamUtilities.BodySegmentSize, context.HttpContext.RequestAborted);
195+
await body.CopyToAsync(response.BodyWriter, context.HttpContext.RequestAborted);
196196
}
197197
catch (OperationCanceledException)
198198
{
@@ -343,19 +343,19 @@ internal void FinalizeCacheBody(ResponseCachingContext context)
343343
if (context.ShouldCacheResponse && context.ResponseCachingStream.BufferingEnabled)
344344
{
345345
var contentLength = context.HttpContext.Response.ContentLength;
346-
var bufferStream = context.ResponseCachingStream.GetBufferStream();
347-
if (!contentLength.HasValue || contentLength == bufferStream.Length
348-
|| (bufferStream.Length == 0
346+
var cachedResponseBody = context.ResponseCachingStream.GetCachedResponseBody();
347+
if (!contentLength.HasValue || contentLength == cachedResponseBody.Length
348+
|| (cachedResponseBody.Length == 0
349349
&& HttpMethods.IsHead(context.HttpContext.Request.Method)))
350350
{
351351
var response = context.HttpContext.Response;
352352
// Add a content-length if required
353353
if (!response.ContentLength.HasValue && StringValues.IsNullOrEmpty(response.Headers[HeaderNames.TransferEncoding]))
354354
{
355-
context.CachedResponse.Headers[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(bufferStream.Length);
355+
context.CachedResponse.Headers[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(cachedResponseBody.Length);
356356
}
357357

358-
context.CachedResponse.Body = bufferStream;
358+
context.CachedResponse.Body = cachedResponseBody;
359359
_logger.ResponseCached();
360360
_cache.Set(context.StorageVaryKey ?? context.BaseKey, context.CachedResponse, context.CachedResponseValidFor);
361361
}

src/Middleware/ResponseCaching/src/Streams/ResponseCachingStream.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ public override long Position
4545
}
4646
}
4747

48-
internal Stream GetBufferStream()
48+
internal CachedResponseBody GetCachedResponseBody()
4949
{
5050
if (!BufferingEnabled)
5151
{
5252
throw new InvalidOperationException("Buffer stream cannot be retrieved since buffering is disabled.");
5353
}
54-
return new SegmentReadStream(_segmentWriteStream.GetSegments(), _segmentWriteStream.Length);
54+
return new CachedResponseBody(_segmentWriteStream.GetSegments(), _segmentWriteStream.Length);
5555
}
5656

5757
internal void DisableBuffering()

0 commit comments

Comments
 (0)