From 399f37c6812f71066d944c5319164126def16204 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 31 Aug 2023 15:27:06 -0700
Subject: [PATCH] [release/8.0] [DependencyInjection] introduce feature switch
to disable S.R.E (#91352)
* [DependencyInjection] introduce feature switch to disable S.R.E
When recording a new AOT profile for .NET MAUI apps running on Android,
we noticed that System.Reflection.Emit work was being done on a
background thread. The call seen in `dotnet-trace` output:
11.32ms microsoft.extensions.dependencyinjection!Microsoft.Extensions.DependencyInjection.ServiceLookup.ILEmitResolverBuilder.GenerateMethodBody(Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceCallSite,System.Reflection.Emit.ILGenerator)
.NET Android apps are unique in that there is a JIT,
`RuntimeFeature.IsDynamicCodeCompiled` is true, System.Reflection.Emit
is possible -- S.R.E is however, not great for startup performance.
Starting threads on Android during startup can also be slow, as Android
will commonly put all but a single core to sleep for battery saving
purposes. We try to avoid starting threads on startup for "hello world"
applications on Android.
To solve this for now, introduce a new feature flag:
Microsoft.Extensions.DependencyInjection.DisableDynamicEngine
Which, we will provide a value in either the Android or .NET MAUI
optional workload via an MSBuild property. To test, I put this in my
app's `.csproj` file:
Customers *could* opt to change this flag, but we don't think it will
particularly useful. An example of services realized by .NET MAUI at
startup, via some logging added:
08-25 13:21:55.647 16530 16530 I DOTNET : RealizeService called: System.Collections.Generic.IEnumerable`1[Microsoft.Maui.Hosting.IMauiInitializeService]
08-25 13:21:55.664 16530 16530 I DOTNET : RealizeService called: System.Collections.Generic.IEnumerable`1[Microsoft.Maui.Hosting.IMauiInitializeScopedService]
08-25 13:21:55.665 16530 16530 I DOTNET : RealizeService called: Microsoft.Maui.Dispatching.IDispatcher
08-25 13:21:55.668 16530 16530 I DOTNET : RealizeService called: System.Collections.Generic.IEnumerable`1[Microsoft.Maui.LifecycleEvents.LifecycleEventRegistration]
08-25 13:21:56.057 16530 16530 I DOTNET : RealizeService called: System.Collections.Generic.IEnumerable`1[Microsoft.Maui.Hosting.HandlerMauiAppBuilderExtensions+HandlerRegistration]
08-25 13:21:56.115 16530 16530 I DOTNET : RealizeService called: Microsoft.Extensions.DependencyInjection.IServiceScopeFactory
08-25 13:21:56.670 16530 16530 I DOTNET : RealizeService called: Microsoft.Maui.Controls.HideSoftInputOnTappedChangedManager
08-25 13:21:56.712 16530 16530 I DOTNET : RealizeService called: System.Collections.Generic.IEnumerable`1[Microsoft.Maui.Hosting.ImageSourcesMauiAppBuilderExtensions+ImageSourceRegistration]
08-25 13:21:57.700 16530 16530 I DOTNET : RealizeService using S.R.E: Microsoft.Maui.Controls.HideSoftInputOnTappedChangedManager
`HideSoftInputOnTappedChangedManager` would be realized once per page,
which would not be a huge payoff to use S.R.E for. So the only way the
S.R.E codepath could be useful on Android would be if the customer is
registering lots of services themselves. They might be better off just
using `new()` in that case?
An example of the startup time Android reports with the new flag on/off:
DisableDynamicEngine=false
08-25 14:31:37.462 2090 2330 I ActivityTaskManager: Displayed com.companyname.testmaui/crc643c09abdeec717b83.MainActivity: +733ms
08-25 14:31:39.394 2090 2330 I ActivityTaskManager: Displayed com.companyname.testmaui/crc643c09abdeec717b83.MainActivity: +737ms
08-25 14:31:41.326 2090 2330 I ActivityTaskManager: Displayed com.companyname.testmaui/crc643c09abdeec717b83.MainActivity: +730ms
DisableDynamicEngine=true
08-25 14:32:20.233 2090 2330 I ActivityTaskManager: Displayed com.companyname.testmaui/crc643c09abdeec717b83.MainActivity: +724ms
08-25 14:32:22.137 2090 2330 I ActivityTaskManager: Displayed com.companyname.testmaui/crc643c09abdeec717b83.MainActivity: +727ms
08-25 14:32:24.042 2090 2330 I ActivityTaskManager: Displayed com.companyname.testmaui/crc643c09abdeec717b83.MainActivity: +716ms
This was a `dotnet new maui` project, using dotnet/maui/main on a Pixel
5 device.
* Update docs/workflow/trimming/feature-switches.md
Co-authored-by: Eric Erhardt
---------
Co-authored-by: Jonathan Peppers
Co-authored-by: Jonathan Peppers
Co-authored-by: Eric Erhardt
---
docs/workflow/trimming/feature-switches.md | 1 +
.../src/ILLink/ILLink.Substitutions.xml | 3 +++
.../src/ServiceProvider.cs | 5 ++++-
3 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/docs/workflow/trimming/feature-switches.md b/docs/workflow/trimming/feature-switches.md
index 87d8fa4c5ec..7fa3045547e 100644
--- a/docs/workflow/trimming/feature-switches.md
+++ b/docs/workflow/trimming/feature-switches.md
@@ -27,6 +27,7 @@ configurations but their defaults might vary as any SDK can set the defaults dif
| MetadataUpdaterSupport | System.Reflection.Metadata.MetadataUpdater.IsSupported | Metadata update related code to be trimmed when set to false |
| _EnableConsumingManagedCodeFromNativeHosting | System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting | Getting a managed function from native hosting is disabled when set to false and related functionality can be trimmed. |
| VerifyDependencyInjectionOpenGenericServiceTrimmability | Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability | When set to true, DependencyInjection will verify trimming annotations applied to open generic services are correct |
+| DisableDependencyInjectionDynamicEngine | Microsoft.Extensions.DependencyInjection.DisableDynamicEngine | When set to true, DependencyInjection will avoid using System.Reflection.Emit when realizing services |
| NullabilityInfoContextSupport | System.Reflection.NullabilityInfoContext.IsSupported | Nullable attributes can be trimmed when set to false |
| DynamicCodeSupport | System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported | Changes RuntimeFeature.IsDynamicCodeSupported to false to allow testing AOT-safe fallback code without publishing for Native AOT. |
| _AggressiveAttributeTrimming | System.AggressiveAttributeTrimming | When set to true, aggressively trims attributes to allow for the most size savings possible, even if it could result in runtime behavior changes |
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ILLink/ILLink.Substitutions.xml b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ILLink/ILLink.Substitutions.xml
index eb381de19d6..6aa354ee236 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ILLink/ILLink.Substitutions.xml
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ILLink/ILLink.Substitutions.xml
@@ -3,5 +3,8 @@
+
+
+
diff --git a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs
index aa178741fc4..ff5efbe98cf 100644
--- a/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs
+++ b/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs
@@ -37,6 +37,9 @@ public sealed class ServiceProvider : IServiceProvider, IKeyedServiceProvider, I
internal static bool VerifyOpenGenericServiceTrimmability { get; } =
AppContext.TryGetSwitch("Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability", out bool verifyOpenGenerics) ? verifyOpenGenerics : false;
+ internal static bool DisableDynamicEngine { get; } =
+ AppContext.TryGetSwitch("Microsoft.Extensions.DependencyInjection.DisableDynamicEngine", out bool disableDynamicEngine) ? disableDynamicEngine : false;
+
internal static bool VerifyAotCompatibility =>
#if NETFRAMEWORK || NETSTANDARD2_0
false;
@@ -246,7 +249,7 @@ private ServiceProviderEngine GetEngine()
#if NETFRAMEWORK || NETSTANDARD2_0
engine = CreateDynamicEngine();
#else
- if (RuntimeFeature.IsDynamicCodeCompiled)
+ if (RuntimeFeature.IsDynamicCodeCompiled && !DisableDynamicEngine)
{
engine = CreateDynamicEngine();
}
--
GitLab