[release/8.0] [DependencyInjection] introduce feature switch to disable S.R.E (#91352)
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Thu, 31 Aug 2023 22:27:06 +0000 (15:27 -0700)
committerGitHub <noreply@github.com>
Thu, 31 Aug 2023 22:27:06 +0000 (15:27 -0700)
commit399f37c6812f71066d944c5319164126def16204
treec366de2e26d899cefa254a65c39ff50e2911b118
parent9c422ed36a6f68a1940cc737d4a173bafd27467c
[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:

    <RuntimeHostConfigurationOption Include="Microsoft.Extensions.DependencyInjection.DisableDynamicEngine"
                                    Condition="'$(DisableDynamicEngine)' != ''"
                                    Value="$(DisableDynamicEngine)"
                                    Trim="true" />

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 <eric.erhardt@microsoft.com>
---------

Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>
Co-authored-by: Jonathan Peppers <jonathan.peppers@gmail.com>
Co-authored-by: Eric Erhardt <eric.erhardt@microsoft.com>
docs/workflow/trimming/feature-switches.md
src/libraries/Microsoft.Extensions.DependencyInjection/src/ILLink/ILLink.Substitutions.xml
src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs