Magnus Rahl
Nov 14, 2023
  5127
(6 votes)

Support for .NET 8

As .NET 8 is released at Dotnetconf today, me and the teams are also happy to announce support for .NET 8 in Optimizely products!

We have tested the latest CMS, Commerce and Search & Navigation packages as well as the most common Optimizely provided addons using the .NET 8 release candidates. After a quick verification with the RTM version of .NET 8 I can now officially say that we support .NET 8.

Our DXP cloud service also supports running .NET 8 applications. Which runtime version is used is determined by which version you compile your project for, through the metadata that the build outputs to the [ProjectName].runtimeconfig.json file. If you want to you can also update the transitive dependencies from our packages to the Microsoft.Extensions.* packages. We have tested running in .NET 8 both with the lowest dependency version we require (6.0) and the 8.0 versions of those.

As mentioned before we continue to release assemblies compiled for .NET 6, as it is not required for us as library authors to change our compilation target for you to run in .NET 8 now that we have verified compatibility. We will change our compilation target too in the future, most likely as part of a future major version release.

If you run into any issues, as usual contact support.

Now, go get .NET 8 and build even better applications with Optimizely!

Nov 14, 2023

Comments

Johan Book
Johan Book Nov 15, 2023 07:06 AM

Great news! Thanks for this update!

Otto G
Otto G Nov 15, 2023 02:07 PM

Hi Johan, this sounds great! However, I just tested an Optimizely website locally under .NET 8, and once the website is up and running and responding to requests, I get constant console messages like this (every 2 seconds or so):

info: EPiServer.Framework.Cache.Internal.MemoryCacheCompactor[0]
      High memory pressure event received.
info: EPiServer.Framework.Cache.Internal.MemoryCacheCompactor[0]
      Waiting for a Gen 2 GC before compacting the cache again.

When compiling and running the exact same website (with the latest versions of all Nuget packages) under .NET 6.0 or 7.0, there are no such messages printed.

Should this be a source of concern, or is it just something with .NET 8 that causes info messages to be printed for things that have been happening silently in previous versions?

Also, do you know if DXP hosting environments will automatically be prepared for .NET 8, or is it done upon request?

By the way, I noticed that authentication using Microsoft.AspNetCore.Authentication.WsFederation that works with 6.0 and 7.0 may fail with .NET 8.0. I guess that this issue is unlikely to be Optimzely-related, though.

Otto G
Otto G Nov 15, 2023 02:09 PM

Hi Magnus, this sounds great! However, I just tested an Optimizely website locally under .NET 8, and once the website is up and running and responding to requests, I get constant console messages like this (every 2 seconds or so):

info: EPiServer.Framework.Cache.Internal.MemoryCacheCompactor[0]
      High memory pressure event received.
info: EPiServer.Framework.Cache.Internal.MemoryCacheCompactor[0]
      Waiting for a Gen 2 GC before compacting the cache again.

When compiling and running the exact same website (with the latest versions of all Nuget packages) under .NET 6.0 or 7.0, there are no such messages printed.

Should this be a source of concern, or is it just something with .NET 8 that causes info messages to be printed for things that have been happening silently in previous versions?

Also, do you know if DXP hosting environments will automatically be prepared for .NET 8, or is it done upon request?

By the way, I noticed that authentication using Microsoft.AspNetCore.Authentication.WsFederation that works with 6.0 and 7.0 may fail with .NET 8.0. I guess that this issue is unlikely to be Optimizely-related, though.

Otto G
Otto G Nov 15, 2023 02:15 PM

Sorry for the double almost identical comments – the delete button gives a jQuery error message: “TypeError: undefined is not an object (evaluating 'c._buttons')”

Magnus Rahl
Magnus Rahl Nov 15, 2023 02:26 PM

Thanks for bringing this to our attention. When running locally it could just have been your computer being on high total memory load at that time. But we will definitely look into it, I know there have been changes in how the GC works in .NET 8.

DXP will automatically select the runtime based on your compilation target as mentioned in the article.

There may very well be breaking changes / issues in .NET itself or surrounding packages because they are new major versions.

Otto G
Otto G Nov 15, 2023 03:07 PM

The first website I tried has a rather large content database. I have now also tested a smaller website in terms of content, which has a similar code base.

When running that second website locally under .NET 8.0, it takes a little while and perhaps 10 or 20 page requests before the GC messages start to appear. With the bigger website, they appear immediately after the first request, or at the latest after a few requests.

Both websites run without GC messages when targeting .NET 7.0. I can stop either website, edit the csproj file to change the targeting back and forth, and build and run the website again – and if I changed to 8.0, the GC info messages appear, and if I changed to 7.0, they do not appear.

So, the overall memory load on the computer is the same in both cases. It is under macOS 14 Sonoma, by the way.

Magnus Rahl
Magnus Rahl Nov 15, 2023 06:09 PM

My investigations so far show that at least on DXP (i.e. in a Linux container) it gets the memory measurement correct on .NET 8. It reports the same memory load level as the azure monitoring.

The event you see is when it detects memory load above what it sees as high (default 90% IIRC) and therefore compacts the cache. If you enable debug logging on the EPiServer.Hosting.Internal namespace you should see the actual percentage value reported. Note that this will be 0 until the first GC has happened.

Jørgen Helgheim
Jørgen Helgheim Nov 15, 2023 07:43 PM

This is great. I change the target from .NET 6 to .NET 8 today asweel and with simples checks it indicates a good running application (CMS and Customized Commerce B2C). 

I have the same info messages mentioned by Otto G, but I had them already in NET 6, so.. I'm planing to take a look at https://docs.developers.optimizely.com/content-management-system/docs/monitoring-memorycache for this issue. 

Otto G
Otto G Nov 16, 2023 10:45 AM

Hi again, when I turn on the debugging, the messages that appear after a few pages have been served (or sometimes before even a single page has been served) indicate “99 % of 24576 MB used”. 24 GB is the total amount of RAM installed in the computer. They go directly from no messages to 99%, and even after reportedly compacting the cache, the next message that comes shortly afterwards still says 99%.

I can monitor the process with the “top” command, and the memory consumption tends to be around 160 MB (differs somewhat between runs), and there is no real difference before and after the messages start appearing.

Before the messages have started:

PID    COMMAND      %CPU TIME     #TH  #WQ  #POR MEM   PURG CMPR PGRP  PPID STATE    BOOSTS    %CPU_ME %CPU_OTHRS UID  FAULTS COW  MSGSE
46073  dotnet       0.0  00:01.54 30   1    114  159M  0B   0B   46073 821  sleeping *0[1]     0.00000 0.00000    502  18872  359  6632

After the messages have started appearing:

PID    COMMAND      %CPU TIME     #TH  #WQ  #POR MEM   PURG CMPR PGRP  PPID STATE    BOOSTS    %CPU_ME %CPU_OTHRS UID  FAULTS COW  MSGSE
46073  dotnet       0.0  00:01.65 24   1    96   159M  0B   73M  46073 821  sleeping *0[1]     0.00000 0.00000    502  19007  359  6676

Here is part of the output from the same “dotnet run” invocation, with debug logging enabled, starting from the first memory-related line (these messages all appeared within a few seconds):

dbug: EPiServer.Hosting.Internal.GCMemoryMonitor[0]
      99 % of 24576 MB used.
info: EPiServer.Framework.Cache.Internal.MemoryPressureMonitor[0]
      Current highest memory pressure level reported by a memory monitor is High. Adjusting memory status poll interval to 1000 ms.
info: EPiServer.Framework.Cache.Internal.MemoryCacheCompactor[0]
      High memory pressure event received.
info: EPiServer.Framework.Cache.Internal.MemoryCacheCompactor[0]
      Initiating cache compacting removing 10 % of cached items.
info: EPiServer.Framework.Cache.Internal.MemoryCacheCompactor[0]
      Finished compacting cache in 6 ms.
dbug: EPiServer.Hosting.Internal.GCMemoryMonitor[0]
      99 % of 24576 MB used.
info: EPiServer.Framework.Cache.Internal.MemoryCacheCompactor[0]
      High memory pressure event received.
info: EPiServer.Framework.Cache.Internal.MemoryCacheCompactor[0]
      Waiting for a Gen 2 GC before compacting the cache again.

Otto G
Otto G Nov 16, 2023 10:51 AM

For completeness, here is a screen dump of the macOS activity monitor, right after a CMS process has stared outputting these messages – no visible effect on the overall memory pressure chart.

Otto G
Otto G Nov 16, 2023 11:00 AM

Under .NET 7.0, the memory consumption reported by “top” is about the same. With logging on, occasional memory debug messages are emitted, and they tend to show around 67%.

Output from “top”:

PID    COMMAND      %CPU TIME     #TH  #WQ  #POR MEM   PURG CMPR PGRP  PPID STATE    BOOSTS    %CPU_ME %CPU_OTHRS UID  FAULTS COW  MSGSE
46655  dotnet       0.0  00:01.70 23   1    91   166M  0B   72M  46655 821  sleeping *0[1]     0.00000 0.00000    502  21618  360  7423

Memory debug messages:

dbug: EPiServer.Hosting.Internal.GCMemoryMonitor[0]
      68 % of 24576 MB used.

dbug: EPiServer.Hosting.Internal.GCMemoryMonitor[0]
      67 % of 24576 MB used.

Magnus Rahl
Magnus Rahl Nov 16, 2023 12:39 PM

Ok, so that sounds like it is a Mac OS specific .NET 8 issue. The 67 % you see seems to approximately match the memory pressure you see in the GUI. We're using the output from the GC.GetGCMemoryInfo method. It is the total "physical" memory it looks at (or container limits). What you're seeing as a percentage is the ratio between the reported MemoryLoadBytes and TotalAvailableMemoryBytes. It seems like MemoryLoadBytes is then returned incorrectly on your Mac when running in .NET 8. So could be a bug in .NET. Not disasterous for a dev environment, and in DXP production environments as mentioned it seems to work correctly.

Otto G
Otto G Nov 16, 2023 04:07 PM

Yes that seems to be the case. I have now installed the website under a Ubuntu virtual machine, connected to the same database. The memory usage shown in the debug messages slowly increases from about 25% to about 30% during use of the website. The virtual machine is configured with 4 GB of RAM, and the debug messages show 3911 MB as the total available. This seems more reasonable – but “top” shows just 5.1% in the memory column for the process, though. I am not sure exactly how to interpret the values from “top” in this context. But it does seem like a lot if the process indeed uses around 1 GB right from the start, increasing to around 1.2 GB, so possibly, the numbers are a bit off under Linux, too.

Sample debug printout:

dbug: EPiServer.Hosting.Internal.GCMemoryMonitor[0]
    30 % of 3911 MB used.

Output from “top”:

top - 16:00:45 up  2:37,  2 users,  load average: 0.00, 0.02, 0.00
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.3 us,  0.3 sy,  0.0 ni, 99.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   3911.5 total,    566.5 free,    851.2 used,   2493.9 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.   2714.6 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                             
   2906 ottog     20   0  260.6g 205964 118872 S   0.0   5.1   0:02.88 dotnet                                                              

Magnus Rahl
Magnus Rahl Nov 16, 2023 04:15 PM

How the GC measures the available memory and how that relates to what top will show you is way beyond my knowledge. But just pointing out again that the percentage is supposed to represent the total memory load on the "machine" (or container limit), including all other running processes, the OS itself etc. So there is no contradiction in the reported 30% memory usage on a freshly started container even if the site process running the site itself only uses 200 MB or so. The memory monitoring is designed for production scenarios when the site is the primary thing running on the host, so it makes sense to monitor the overall memory usage of the host rather than of the process.

Otto G
Otto G Nov 16, 2023 04:30 PM

Ah, right, I misunderstood the explanation of the percentage part of the debug printouts in the previous comments. Thank you for clarifying. Then it does seem plausible that the figures are right under Linux with .NET 8.0. That is reassuring.

The macOS bug is a bit annoying when testing things locally, though, and I wonder how it might affect testing of code that uses https://learn.microsoft.com/en-us/aspnet/core/performance/caching/output?view=aspnetcore-8.0 and/or ISynchronizedObjectInstanceCache. (That is, if things will repeatedly and frequently be evicted from the caches, even though the memory is not actually full.)

Johan Kronberg
Johan Kronberg Nov 17, 2023 03:34 PM

Great to get the performance improvements and to use C# 12 etc but to really be happy we need support for Blazor SSR as well. Please put some prioritization on this, and not just only on the out-of-process setups/SaaS etc, to remain in good light with .NET developers. Still plenty of apps that will run on-prem or in PaaS that will live long with in-process ASP.NET templates.

Help out with votes: https://feedback.optimizely.com/ideas/CMS-I-416

Otto G
Otto G Nov 17, 2023 09:27 PM

Regarding Blazor, I suppose there cannot be much, if anything, that prevents an Optimizely CMS website from being rendered with Blazor SSR templates, though?

Articles like https://medium.com/capgemini-microsoft-team/using-blazor-in-your-existing-asp-net-core-mvc-applications-f1b09bd66f4b indicate that there has been good interoperability with cshtml Razor templates for quite some time. So, they are probably not so different from each other in how they integrate with routing and view resolution concepts in ASP.NET Core. Presumably, some configuration of view engines will be needed, and perhaps adjustment of the ViewResult objects returned by controllers.

Of course, making navigation between pages work like in a typical SPA website could potentially be a lot more work, unless there is already some clever solution to this built into Blazor SSR. (I do not know much about the capabilities of Blazor – I just got curious and wondered whether it really depends on the packaged part of the CMS if you can use Blazor SSR templates or not, or if it is just a matter of writing the website-specific code according to the desired template type.)

And things like Forms, which have a lot of automatic things going on behind the scenes in the cshtml views, may definitely be potential issues. But then again, perhaps they will work even inside of Blazor SSR templates (just a bit differently from other parts of the page), thanks to the similarities in technology.

Perhaps a demo project is needed, more than implementation of support as such?

Johan Kronberg
Johan Kronberg Nov 18, 2023 04:58 PM

I have done a lot of work with Blazor SSR already (using the Preview and RC versions, but not related to EPiServer.CMS).

Even with things are in dotnet 8 like HtmlRenderer().RenderComponentAsync<T>() that allows you to render a page/component into a string from anywhere and new RazorComponentResult<T>(someReadOnlyDictionary()) that you can use to return a "set SSR page/componenent" from MVC Controllers and minimal endpoints I'd say using that in the current CMS version will probably just mean a lot of "hacks" with extra steps and switching modes (between the new lightweight smooth way, and the old *Contexts and Renderers mess when needing to render something) resulting in poor performance.

You won't be able to use EditForm component as designed and you won't get the benefit of putting all code in the .razor-file - you need to model-bind in a separate controller or endpoint instead of using a OnSubmit-handler in the Blazor SSR page/component. Also, Tag and HTML Helpers are not a concept in any Blazor mode.

I wrote that feature request quite long ago but knowing more how dotnet 8 turned out my guess is that the order of things needed to be built by Optimizely to use Blazor SSR is something like this:

  • Tap into routing and select the correct Blazor page to initialize, set a CascadingParameter for CurrentPage.
  • Build a component library for things like rendering XhtmlString and ContentArea properties that locates the wanted component to use for a BlockData instance, adds the on-page-edit stuff etc.

Personally I don't need to any support for the Blazor SPA modes and that type of routing.

I guess having the above SSR support, inititializing an interactive component "island" that uses Server or WASM mode would work without anything else needed.

huseyinerdinc
huseyinerdinc Nov 20, 2023 09:18 AM

Great news! I have tried to upgrade one of the projects but it didn't go well for me. The application threw an exception saying a constructor couldn't be found.  Method not found: 'Void Microsoft.EntityFrameworkCore.Storage.RelationalTypeMapping..ctor

It seems that the fail originates from EPiServer.Cms.UI.AspNetIdentity.ApplicationUserProvider`1.GetUserAsync(String username)

The full stack trace:

System.MissingMethodException: Method not found: 'Void Microsoft.EntityFrameworkCore.Storage.RelationalTypeMapping..ctor(System.String, System.Type, System.Nullable`1<System.Data.DbType>, Boolean, System.Nullable`1<Int32>, Boolean, System.Nullable`1<Int32>, System.Nullable`1<Int32>)'.
         at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerSqlVariantTypeMapping..ctor(String storeType)
         at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerTypeMappingSource..ctor(TypeMappingSourceDependencies dependencies, RelationalTypeMappingSourceDependencies relationalDependencies)
         at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
         at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
         at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
         at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
         at Microsoft.Extensions.DependencyInjection.SqlServerServiceCollectionExtensions.<>c.<AddEntityFrameworkSqlServer>b__1_2(IServiceProvider p)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
         at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
         at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
         at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
         at Microsoft.EntityFrameworkCore.DbContext.get_ContextServices()
         at Microsoft.EntityFrameworkCore.DbContext.get_InternalServiceProvider()
         at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
         at Microsoft.EntityFrameworkCore.DbContext.Set[TEntity]()
         at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9.get_UsersSet()
         at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9.get_Users()
         at Microsoft.AspNetCore.Identity.EntityFrameworkCore.UserStore`9.FindByNameAsync(String normalizedUserName, CancellationToken cancellationToken)
         at Microsoft.AspNetCore.Identity.UserManager`1.FindByNameAsync(String userName)
         at EPiServer.Cms.UI.AspNetIdentity.ApplicationUserProvider`1.GetUserAsync(String username)

Magnus Rahl
Magnus Rahl Nov 23, 2023 01:58 PM

huseyinerdinc did you figure this out? It looks like a version conflict between different versions of entity framework related projects. Do you have multiple projects in your solution referencing different EF package versions perhaps? If you cannot figure it out please send this as a support ticket if you haven't already. 

huseyinerdinc
huseyinerdinc Dec 1, 2023 07:59 AM

Magnus Rahl, not yet. We will revisit this when it's time to upgrade the site to .NET 8. We don't have any other projects in the solution but we do reference the designer package to be able to use migrations.

        <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.16">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>

Johan Book
Johan Book Dec 11, 2023 11:07 AM

When we run the VS profiler during startup we can see that there seems to be a lot of allocations going on in EPiServer.Web.Routing.ContentEndpointRouteBuilderExtensions.MapContent()

In fact, 2.3M allocations (!), 145 MB of memory and this represent about 1/4 of the total memory allocated by the app during startup.

Not sure if this is intentional or if there is logic in your route builder extension that has run amoc.

We're on EPiServer.Cms.AspNetCore.Routing 12.19.1.

Johan Book
Johan Book Dec 11, 2023 11:15 AM

Sorry, was not able to edit my post (or delete it for some reason), I mean to say these tests were made with .NET 6 and the routing extensions represent 1/4 of the _allocations_ (not the actual memory consumption).

I will report this as a support ticket, just thought it was interesting finding with regards to the discussion.

Johan Book
Johan Book Jan 7, 2024 07:36 PM

CMS-31285 was raised with the development team re: heavy allocations during startup.

Apparently there is not that much that CMS allocates itself. Most allocations happen when registering endpoints for all shell modules using ASP.NET Core API, which is where the allocations are said to happen.

Eric
Eric Mar 14, 2024 09:56 PM

We too are getting these errors. We are running on Windows 11.

EPiServer.Framework.Cache.Internal.MemoryCacheCompactor: Information: High memory pressure event received.

Magnus Rahl
Magnus Rahl Mar 15, 2024 07:42 AM

@Eric this is not an error as such. It just means that the memory usage is at or above the target. As a reaction, to avoid running out of memory, the cache will be compacted. This should reduce the memory usage once the items removed from cache are garbage collected. You should see further log messages when a cache compact is initiated, if it is paused waiting for a GC to happen, and when it finishes (these messages may be on debug level, I don't remember). If you keep seeing this repeatedly over a short period time it can mean a few different things:

  • The memory load isn't correctly determined. This was reported on MacOS, and a ticket was opened with Microsoft. I haven't heard this is any problem on Windows or Linux.
  • The GC hasn't been triggered. We don't compact the cache again without a GC run in between as it wouldn't actually free up any more memory. If the GC isn't triggered, you probably don't have a problem becuase the GC has the same goal as our cache compact algorithm - it frees up memory when memory pressure is high and becomes more aggressive the higher the memory pressure is.
  • Something is rapidly filling up the cache. It is normal that the cache goes through continued cycles of growth-compact-growth-compact "bouncing" against the memory usage threshold (defualt 90 %). If it happens too quickly though it is a sign something isn't really right and you are churning through the cache too quickly.
  • There is a bug somewhere, in the implementation or in the product that prevents compacting and GC:ing. For example something other than the cache is using up all memory. The cache compactor reacts to the aggregate physical memory load of the host (machine or vs container limits), but can of course only free up whatever it has in the cache, not outside it. Another reason could be that something else holds reference to the items in the cache so they cannot be GC:ed.

Magnus N
Magnus N Apr 18, 2024 09:12 AM

I recently went from using windows 11 to macOS and since then i have run into the same problem as mentioned above. We have a rather large CMS soulution (around 40 or so start pages) that runs on .Net 8 and a database hosted in Azure. After the first few requests when starting the project, Im constantly (every 1000ms) getting a dbug message prompted in the console saying "99% of 18432 MB used" followed by "Waiting for a Gen 2 GC before compacting the cache again". It may take up to 5min to load a single page, which makes the site completely unusable.

If i run the solution through a virtual desktop (Parallels), with 14gb memory allocated to windows, i still get a high memory load (around 60-90%), but the site is usable and responsive. Is there perhaps a memory leak somewhere? I really hope this problem is looked into, as running parallels for developing is not optimal. If someone has a solution or any tips to this problem im open for suggestions. 

Specs: Macbook Pro M3 Pro chip, 18gb memory, running macOS Sonoma 14.3

Otto G
Otto G Apr 18, 2024 10:50 AM

@Magnus N, it seems that you are suffering from the very same problem that I mentioned, but since your CMS solution is so big, the frequent garbage collection attempts caused by the memory load reporting bug may have a tangible performance impact. That is, there are almost certainly no memory leaks, but there is a constant waste of system resources that can cause slowdowns.

In my case, I just disable the warning printouts by setting the log level for EPiServer.Hosting.Internal and EPiServer.Framework.Cache.Internal to Error, and the moderatly sized solutions that I work on run just fine, as the garbage collection triggered every second or so completes fast enough not to cause any problems.

Unfortunately, it seems that the supposed fix in .NET (see https://github.com/dotnet/runtime/issues/94846 and https://github.com/dotnet/runtime/pull/97102 – merged to the main branch in late January) has not yet made it into any public release.

And one thing that I unfortunately noticed only now – the pull request was “added to the 9.0.0 milestone”. If there is no intention to release this as a bug fix to .NET 8.0.x, we might have to wait until late 2025 before Optimizely CMS works properly on macOS with a long-term-support .NET release. (I guess one could run .NET 9 locally once it is released, but assuming that one still wants to keep using .NET 8 for production until the next LTS release, it would be really messy with regard to source code management – unless there is a way to override framework targeting without editing the csproj file.)

As the GitHub issue is now closed, I have no idea where to initiate a request for considering this fix for inclusion into a .NET 8 bug fix release. I think it is arguably a true bug that should be fixed in a minor release, not some kind of unfortunate “feature” of .NET 8 that must be kept, in case people rely on it. Because .NET is supposed to be cross-platform, and if anyone relies on the MemoryLoadBytes metric always being around 99% of the total installed memory, their code will fail on any other platform than macOS…

So, if anyone knows where in GitHub to properly open a request for fixing this already in an 8.0.x release, please do so and post a message here, and I will be happy to add a comment in GitHub supporting the petition.

Please login to comment.
Latest blogs
Increase timeout for long running SQL queries using SQL addon

Learn how to increase the timeout for long running SQL queries using the SQL addon.

Tomas Hensrud Gulla | Dec 20, 2024 | Syndicated blog

Overriding the help text for the Name property in Optimizely CMS

I recently received a question about how to override the Help text for the built-in Name property in Optimizely CMS, so I decided to document my...

Tomas Hensrud Gulla | Dec 20, 2024 | Syndicated blog

Resize Images on the Fly with Optimizely DXP's New CDN Feature

With the latest release, you can now resize images on demand using the Content Delivery Network (CDN). This means no more storing multiple versions...

Satata Satez | Dec 19, 2024

Simplify Optimizely CMS Configuration with JSON Schema

Optimizely CMS is a powerful and versatile platform for content management, offering extensive configuration options that allow developers to...

Hieu Nguyen | Dec 19, 2024