From 836f6edbc78d4a0aafc991c47931b910c5367922 Mon Sep 17 00:00:00 2001 From: Johannes Gustafsson Date: Tue, 19 Sep 2017 09:00:44 +0200 Subject: [PATCH] A small test program to demonstrate the memory leak when using StringWriter. Use of a StreamWriter between mashalled calls removes the leak. --- src/RazorEngine.sln | 39 +++++++++++- .../RazorEngineServiceExtensions.cs | 12 ++-- src/test/TestMemory/App.config | 6 ++ src/test/TestMemory/Program.cs | 37 ++++++++++++ .../TestMemory/Properties/AssemblyInfo.cs | 36 +++++++++++ src/test/TestMemory/TestMemory.csproj | 59 +++++++++++++++++++ 6 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 src/test/TestMemory/App.config create mode 100644 src/test/TestMemory/Program.cs create mode 100644 src/test/TestMemory/Properties/AssemblyInfo.cs create mode 100644 src/test/TestMemory/TestMemory.csproj diff --git a/src/RazorEngine.sln b/src/RazorEngine.sln index b9fcccdc..b9ad8b5b 100644 --- a/src/RazorEngine.sln +++ b/src/RazorEngine.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26621.2 +VisualStudioVersion = 15.0.26730.15 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RazorEngine.Core", "source\RazorEngine.Core\RazorEngine.Core.csproj", "{D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}" EndProject @@ -98,82 +98,116 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestHelper", "test\TestHelp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestRunnerHelper", "test\TestRunnerHelper\TestRunnerHelper.csproj", "{6B0E2ECB-01EE-482B-951C-9E0F0D80F030}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestMemory", "test\TestMemory\TestMemory.csproj", "{7E82E3B3-6223-4727-8099-CF4F31D38F10}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|Net40 = Debug|Net40 Debug|Net45 = Debug|Net45 Debug|Razor4 = Debug|Razor4 + Release|Any CPU = Release|Any CPU Release|Net40 = Release|Net40 Release|Net45 = Release|Net45 Release|Razor4 = Release|Razor4 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Debug|Any CPU.ActiveCfg = Debug|Razor4 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Debug|Net40.ActiveCfg = Debug|Net40 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Debug|Net40.Build.0 = Debug|Net40 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Debug|Net45.ActiveCfg = Debug|Net45 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Debug|Net45.Build.0 = Debug|Net45 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Debug|Razor4.ActiveCfg = Debug|Razor4 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Debug|Razor4.Build.0 = Debug|Razor4 + {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Release|Any CPU.ActiveCfg = Release|Razor4 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Release|Net40.ActiveCfg = Release|Net40 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Release|Net40.Build.0 = Release|Net40 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Release|Net45.ActiveCfg = Release|Net45 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Release|Net45.Build.0 = Release|Net45 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Release|Razor4.ActiveCfg = Release|Razor4 {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4}.Release|Razor4.Build.0 = Release|Razor4 + {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Debug|Any CPU.ActiveCfg = Debug|Razor4 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Debug|Net40.ActiveCfg = Debug|Net40 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Debug|Net40.Build.0 = Debug|Net40 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Debug|Net45.ActiveCfg = Debug|Net45 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Debug|Net45.Build.0 = Debug|Net45 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Debug|Razor4.ActiveCfg = Debug|Razor4 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Debug|Razor4.Build.0 = Debug|Razor4 + {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Release|Any CPU.ActiveCfg = Release|Razor4 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Release|Net40.ActiveCfg = Release|Net40 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Release|Net40.Build.0 = Release|Net40 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Release|Net45.ActiveCfg = Release|Net45 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Release|Net45.Build.0 = Release|Net45 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Release|Razor4.ActiveCfg = Release|Razor4 {F4F5AB5F-BF81-4C0C-8F2E-68AB02160C4E}.Release|Razor4.Build.0 = Release|Razor4 + {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Debug|Any CPU.ActiveCfg = Debug|Net45 {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Debug|Net40.ActiveCfg = Debug|Net45 {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Debug|Net45.ActiveCfg = Debug|Net45 {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Debug|Net45.Build.0 = Debug|Net45 {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Debug|Razor4.ActiveCfg = Debug|Net45 + {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Release|Any CPU.ActiveCfg = Release|Net45 {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Release|Net40.ActiveCfg = Release|Net45 {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Release|Net45.ActiveCfg = Release|Net45 {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Release|Net45.Build.0 = Release|Net45 {878A8554-ACF1-4A0B-9BFC-6BD5FC5048C4}.Release|Razor4.ActiveCfg = Release|Net45 + {BC7891A1-8459-4A69-A37C-F40824E28C70}.Debug|Any CPU.ActiveCfg = Debug|Net45 {BC7891A1-8459-4A69-A37C-F40824E28C70}.Debug|Net40.ActiveCfg = Debug|Net45 {BC7891A1-8459-4A69-A37C-F40824E28C70}.Debug|Net45.ActiveCfg = Debug|Net45 {BC7891A1-8459-4A69-A37C-F40824E28C70}.Debug|Net45.Build.0 = Debug|Net45 {BC7891A1-8459-4A69-A37C-F40824E28C70}.Debug|Razor4.ActiveCfg = Debug|Net45 + {BC7891A1-8459-4A69-A37C-F40824E28C70}.Release|Any CPU.ActiveCfg = Release|Net45 {BC7891A1-8459-4A69-A37C-F40824E28C70}.Release|Net40.ActiveCfg = Release|Net45 {BC7891A1-8459-4A69-A37C-F40824E28C70}.Release|Net45.ActiveCfg = Release|Net45 {BC7891A1-8459-4A69-A37C-F40824E28C70}.Release|Net45.Build.0 = Release|Net45 {BC7891A1-8459-4A69-A37C-F40824E28C70}.Release|Razor4.ActiveCfg = Release|Net45 + {7CB02F9F-7E88-448B-A25B-7985058296C5}.Debug|Any CPU.ActiveCfg = Debug|Razor4 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Debug|Net40.ActiveCfg = Debug|Net40 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Debug|Net45.ActiveCfg = Debug|Net45 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Debug|Net45.Build.0 = Debug|Net45 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Debug|Razor4.ActiveCfg = Debug|Razor4 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Debug|Razor4.Build.0 = Debug|Razor4 + {7CB02F9F-7E88-448B-A25B-7985058296C5}.Release|Any CPU.ActiveCfg = Release|Razor4 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Release|Net40.ActiveCfg = Release|Net40 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Release|Net45.ActiveCfg = Release|Net45 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Release|Net45.Build.0 = Release|Net45 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Release|Razor4.ActiveCfg = Release|Razor4 {7CB02F9F-7E88-448B-A25B-7985058296C5}.Release|Razor4.Build.0 = Release|Razor4 + {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Debug|Any CPU.ActiveCfg = Debug|Net45 {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Debug|Net40.ActiveCfg = Debug|Net45 {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Debug|Net45.ActiveCfg = Debug|Net45 {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Debug|Net45.Build.0 = Debug|Net45 {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Debug|Razor4.ActiveCfg = Debug|Net45 + {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Release|Any CPU.ActiveCfg = Release|Net45 {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Release|Net40.ActiveCfg = Release|Net45 {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Release|Net45.ActiveCfg = Release|Net45 {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Release|Net45.Build.0 = Release|Net45 {7899C0AE-844D-4A34-B3BC-96819C73EA68}.Release|Razor4.ActiveCfg = Release|Net45 + {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Debug|Any CPU.ActiveCfg = Debug|Net45 {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Debug|Net40.ActiveCfg = Debug|Net45 {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Debug|Net45.ActiveCfg = Debug|Net45 {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Debug|Net45.Build.0 = Debug|Net45 {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Debug|Razor4.ActiveCfg = Debug|Net45 + {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Release|Any CPU.ActiveCfg = Release|Net45 {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Release|Net40.ActiveCfg = Release|Net45 {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Release|Net45.ActiveCfg = Release|Net45 {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Release|Net45.Build.0 = Release|Net45 {6B0E2ECB-01EE-482B-951C-9E0F0D80F030}.Release|Razor4.ActiveCfg = Release|Net45 + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Debug|Net40.ActiveCfg = Debug|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Debug|Net40.Build.0 = Debug|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Debug|Net45.ActiveCfg = Debug|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Debug|Net45.Build.0 = Debug|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Debug|Razor4.ActiveCfg = Debug|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Debug|Razor4.Build.0 = Debug|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Release|Any CPU.Build.0 = Release|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Release|Net40.ActiveCfg = Release|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Release|Net40.Build.0 = Release|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Release|Net45.ActiveCfg = Release|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Release|Net45.Build.0 = Release|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Release|Razor4.ActiveCfg = Release|Any CPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10}.Release|Razor4.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -192,9 +226,10 @@ Global {7CB02F9F-7E88-448B-A25B-7985058296C5} = {AE7E811A-2B9F-45BC-9752-0FB4D1DEE12D} {7899C0AE-844D-4A34-B3BC-96819C73EA68} = {AE7E811A-2B9F-45BC-9752-0FB4D1DEE12D} {6B0E2ECB-01EE-482B-951C-9E0F0D80F030} = {AE7E811A-2B9F-45BC-9752-0FB4D1DEE12D} + {7E82E3B3-6223-4727-8099-CF4F31D38F10} = {AE7E811A-2B9F-45BC-9752-0FB4D1DEE12D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {87BAAA5F-5CFE-4284-91D8-C3C5F4083599} EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.0\lib\NET35 + SolutionGuid = {87BAAA5F-5CFE-4284-91D8-C3C5F4083599} EndGlobalSection EndGlobal diff --git a/src/source/RazorEngine.Core/Templating/RazorEngineServiceExtensions.cs b/src/source/RazorEngine.Core/Templating/RazorEngineServiceExtensions.cs index 303536bb..ee88a35b 100644 --- a/src/source/RazorEngine.Core/Templating/RazorEngineServiceExtensions.cs +++ b/src/source/RazorEngine.Core/Templating/RazorEngineServiceExtensions.cs @@ -217,11 +217,15 @@ public static void RunCompile(this IRazorEngineService service, string templateS /// private static string WithWriter(Action withWriter) { - using (var writer = new System.IO.StringWriter()) - { + // There seems to be something wrong with using a StringWriter when marshalling it to the other AppDomain. + // The internal StringBuilder instance inside StringWriter will not get GC:ed until several minutes later, + // resulting in OutOfMemoryException on high load. If we use a StreamWriter instead, the problem will go away. + var ms = new MemoryStream(); + + using (var writer = new StreamWriter(ms)) withWriter(writer); - return writer.ToString(); - } + + return System.Text.Encoding.UTF8.GetString(ms.ToArray()); } /// diff --git a/src/test/TestMemory/App.config b/src/test/TestMemory/App.config new file mode 100644 index 00000000..9d2c7adf --- /dev/null +++ b/src/test/TestMemory/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/test/TestMemory/Program.cs b/src/test/TestMemory/Program.cs new file mode 100644 index 00000000..9a5b4f83 --- /dev/null +++ b/src/test/TestMemory/Program.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using RazorEngine.Templating; + +namespace TestMemory +{ + [Serializable] + public class TemplateModel + { + public string Name { get; set; } + } + + class Program + { + static void Main(string[] args) + { + using (var service = IsolatedRazorEngineService.Create()) + { + service.AddTemplate("TestTemplate", + "Hello @Model.Name " + new String(Enumerable.Repeat('A', 100000).ToArray())); + service.Compile("TestTemplate", typeof(TemplateModel)); + + int counter = 0; + + while (!Console.KeyAvailable) + { + var result = service.Run("TestTemplate", typeof(TemplateModel), new TemplateModel {Name = "World"}); + Console.WriteLine("Run: {0}. Result: {1}", ++counter, result.Substring(0, 20)); + } + } + + Console.ReadKey(); + Console.WriteLine("Key pressed. Press a key again to quit"); + Console.ReadKey(); + } + } +} diff --git a/src/test/TestMemory/Properties/AssemblyInfo.cs b/src/test/TestMemory/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..e96c8f52 --- /dev/null +++ b/src/test/TestMemory/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("TestMemory")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("TestMemory")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7e82e3b3-6223-4727-8099-cf4f31d38f10")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/test/TestMemory/TestMemory.csproj b/src/test/TestMemory/TestMemory.csproj new file mode 100644 index 00000000..12a5d510 --- /dev/null +++ b/src/test/TestMemory/TestMemory.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {7E82E3B3-6223-4727-8099-CF4F31D38F10} + Exe + TestMemory + TestMemory + v4.7 + 512 + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {D268F86D-2DAB-4329-A75F-3BCF6D5BCDC4} + RazorEngine.Core + + + + \ No newline at end of file