From 13e76c24d3003c21156ffc17ed0e50b005c9b6d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Mon, 3 Jul 2017 23:59:48 +0200 Subject: [PATCH] Dispose CancellationTokenSource created in CacheTagHelper (#6293) Fixes #5649 --- .../CacheTagHelper.cs | 16 +++-- .../DistributedCacheTagHelper.cs | 2 +- .../CacheTagHelperTest.cs | 61 ++++++++++++++++--- 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs index 48798e76c1..1ca3ca9bc1 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs @@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers try { // The entry is set instead of assigning a value to the - // task so that the expiration options are are not impacted + // task so that the expiration options are not impacted // by the time it took to compute it. using (var entry = MemoryCache.CreateEntry(cacheKey)) @@ -108,18 +108,24 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers content = await result; } + + tcs.SetResult(content); } catch { // Remove the worker task from the cache in case it can't complete. tokenSource.Cancel(); + + // If an exception occurs, ensure the other awaiters + // render the output by themselves. + tcs.SetResult(null); throw; } finally { - // If an exception occurs, ensure the other awaiters - // render the output by themselves. - tcs.SetResult(null); + // The tokenSource needs to be disposed as the MemoryCache + // will register a callback on the Token. + tokenSource.Dispose(); } } else @@ -238,4 +244,4 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs index 94f940e298..ede06d1bfc 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs @@ -105,4 +105,4 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs index af1a97409e..30ea407095 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs @@ -648,7 +648,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Equal(2, calls); } - [Fact] public async Task ProcessAsync_ExceptionInProcessing_DoNotThrowInSubsequentRequests() @@ -743,15 +742,63 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Equal(childContent, tagHelperOutput4.Content.GetContent()); } + [Fact] + public async Task ProcessAsync_WorksForNestedCacheTagHelpers() + { + // Arrange + var expected = "Hello world"; + var cache = new MemoryCache(new MemoryCacheOptions()); + var encoder = new HtmlTestEncoder(); + var cacheTagHelper1 = new CacheTagHelper(cache, encoder) + { + ViewContext = GetViewContext(), + Enabled = true + }; + + var cacheTagHelper2 = new CacheTagHelper(cache, encoder) + { + ViewContext = GetViewContext(), + Enabled = true + }; + + var tagHelperOutput2 = new TagHelperOutput( + "cache", + new TagHelperAttributeList(), + getChildContentAsync: (useCachedResult, _) => + { + var content = new DefaultTagHelperContent(); + content.SetContent(expected); + return Task.FromResult(content); + }); + + var tagHelperOutput1 = new TagHelperOutput( + "cache", + new TagHelperAttributeList(), + getChildContentAsync: async (useCachedResult, _) => + { + var context = GetTagHelperContext("test2"); + var output = tagHelperOutput2; + await cacheTagHelper2.ProcessAsync(context, output); + return await output.GetChildContentAsync(); + }); + + // Act + await cacheTagHelper1.ProcessAsync(GetTagHelperContext(), tagHelperOutput1); + + // Assert + Assert.Equal(encoder.Encode(expected), tagHelperOutput1.Content.GetContent()); + } + private static ViewContext GetViewContext() { var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); - return new ViewContext(actionContext, - Mock.Of(), - new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()), - Mock.Of(), - TextWriter.Null, - new HtmlHelperOptions()); + return new ViewContext( + actionContext, + Mock.Of(), + new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()), + Mock.Of(), + TextWriter.Null, + new HtmlHelperOptions()); } private static TagHelperContext GetTagHelperContext(string id = "testid")