Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Proposal]: Startup activation of singletons #86075

Closed
sebastienros opened this issue May 10, 2023 · 15 comments
Closed

[API Proposal]: Startup activation of singletons #86075

sebastienros opened this issue May 10, 2023 · 15 comments
Assignees
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-Extensions-DependencyInjection
Milestone

Comments

@sebastienros
Copy link
Member

Background and motivation

Currently, singleton dependencies are resolved at runtime when they are first requested. This could be an issue for response times and one might want them to be ready when the application starts. Another scenario is when the creation of a singleton should not block multiple requests that could be executed in parallel and be blocked (thundering herd).

The proposal is to create new extension methods that will allow to register auto-activated dependencies. Internally it would use a specifically crafted IHostedService that would resolve them preemptively.

API Proposal

namespace Microsoft.Extensions.DependencyInjection;

public static class AutoActivationExtensions
{
    public static IServiceCollection Activate<TService>(this IServiceCollection services)
        where TService : class
        
    public static IServiceCollection AddActivatedSingleton<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)
        where TService : class
        where TImplementation : class, TService

    public static IServiceCollection AddActivatedSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory)
        where TService : class

    public static IServiceCollection AddActivatedSingleton<TService>(this IServiceCollection services)
        where TService : class

    public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType)

    public static IServiceCollection AddActivatedSingleton<TService, TImplementation>(this IServiceCollection services)
        where TService : class
        where TImplementation : class, TService

    public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory)

    public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType, Type implementationType)

    public static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType)

    public static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType, Type implementationType)

    public static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory)

    public static void TryAddActivatedSingleton<TService>(this IServiceCollection services)
        where TService : class

    public static void TryAddActivatedSingleton<TService, TImplementation>(this IServiceCollection services)
        where TService : class
        where TImplementation : class, TService

    public static void TryAddActivatedSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory)
        where TService : class
}

API Usage

// Registers and auto-activates a class
builder.Services.AddActivatedSingleton<WindowsCounters>();

// Auto-activates a pre-registered singleton. If the service is not registered, System.InvalidOperationException is thrown.
builder.Services.AddSingleton<IFakeService, FakeService>().Activate<IFakeService>());

Alternative Designs

User can already get this behavior by using available components: IHostedService.

Risks

No response

@sebastienros sebastienros added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label May 10, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label May 10, 2023
@ghost
Copy link

ghost commented May 10, 2023

Tagging subscribers to this area: @dotnet/area-extensions-dependencyinjection
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and motivation

Currently, singleton dependencies are resolved at runtime when they are first requested. This could be an issue for response times and one might want them to be ready when the application starts. Another scenario is when the creation of a singleton should not block multiple requests that could be executed in parallel and be blocked (thundering herd).

The proposal is to create new extension methods that will allow to register auto-activated dependencies. Internally it would use a specifically crafted IHostedService that would resolve them preemptively.

API Proposal

namespace Microsoft.Extensions.DependencyInjection;

public static class AutoActivationExtensions
{
    public static IServiceCollection Activate<TService>(this IServiceCollection services)
        where TService : class
        
    public static IServiceCollection AddActivatedSingleton<TService, TImplementation>(this IServiceCollection services, Func<IServiceProvider, TImplementation> implementationFactory)
        where TService : class
        where TImplementation : class, TService

    public static IServiceCollection AddActivatedSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory)
        where TService : class

    public static IServiceCollection AddActivatedSingleton<TService>(this IServiceCollection services)
        where TService : class

    public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType)

    public static IServiceCollection AddActivatedSingleton<TService, TImplementation>(this IServiceCollection services)
        where TService : class
        where TImplementation : class, TService

    public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory)

    public static IServiceCollection AddActivatedSingleton(this IServiceCollection services, Type serviceType, Type implementationType)

    public static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType)

    public static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType, Type implementationType)

    public static void TryAddActivatedSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory)

    public static void TryAddActivatedSingleton<TService>(this IServiceCollection services)
        where TService : class

    public static void TryAddActivatedSingleton<TService, TImplementation>(this IServiceCollection services)
        where TService : class
        where TImplementation : class, TService

    public static void TryAddActivatedSingleton<TService>(this IServiceCollection services, Func<IServiceProvider, TService> implementationFactory)
        where TService : class
}

API Usage

// Registers and auto-activates a class
builder.Services.AddActivatedSingleton<WindowsCounters>();

// Auto-activates a pre-registered singleton. If the service is not registered, System.InvalidOperationException is thrown.
builder.Services.AddSingleton<IFakeService, FakeService>().Activate<IFakeService>());

Alternative Designs

User can already get this behavior by using available components: IHostedService.

Risks

No response

Author: sebastienros
Assignees: -
Labels:

api-suggestion, area-Extensions-DependencyInjection

Milestone: -

@En3Tho
Copy link
Contributor

En3Tho commented May 11, 2023

I guess I see it as a callback like thing when service provider is constructed? If I'm right then maybe this can be generalized?

I mean I saw an implementation idea using a Hosted service but maybe there is a need for a generalized version of this? Some kind of event.

@geeknoid
Copy link
Member

@tekian

@sebastienros
Copy link
Member Author

@cakescience I can see how this would also improve the first request latency, but it might also be counterproductive if you have services that are actually too expensive to initialize and are meant to be used in rare cases (or never...). So, opting-in might be less invasive.

Also, singletons are the ones that are the most prone to taking a lot of time to initialize (pre-fetching configuration/long lived data).

@steveharter steveharter removed the untriaged New issue has not been triaged by the area owner label May 23, 2023
@steveharter steveharter added this to the 8.0.0 milestone May 23, 2023
@TheXenocide
Copy link

to ask if we should consider extending the proposal to the other lifetimes

It seems like the implementation strategy would probably change pretty dramatically to support Scoped lifetimes (unless there is an equivalent to IHostedService that starts/stops whenever a new scope is created/destroyed) and it seems fundamentally incompatible with Transient lifetimes.

It seems this functionality is already present in the preview, is that right? I noticed this was being used by Microsoft.Extensions.Telemetry.Http which is how I found this proposal.

@joperezr
Copy link
Member

@buyaa-n @steveharter any thoughts on this one? This is one of the components we have in dotnet/extensions repo but feel should move down to the platform. It would be really good to get this done by .NET 8 if possible but seems like our window is almost closing for this.

@steveharter steveharter self-assigned this Jul 19, 2023
@steveharter
Copy link
Member

steveharter commented Jul 19, 2023

This overlaps with #43149 and I'll work on closing that [update: done].

@steveharter
Copy link
Member

I'm proposing that we close this issue since there are several workarounds and a new extension: https://github.com/dotnet/extensions/blob/main/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation

@geeknoid
Copy link
Member

I'm proposing that we close this issue since there are several workarounds and a new extension: https://github.com/dotnet/extensions/blob/main/src/Libraries/Microsoft.Extensions.DependencyInjection.AutoActivation

Makes sense to me.

@steveharter
Copy link
Member

steveharter commented Jul 26, 2023

The AutoActivation extension essentially adds the APIs proposed in this issue. However, it doesn't address all scenarios, so I'll suggest moving to Future and not closing. I believe these APIs are common enough to add to the runtime repo, instead of an extension, but since there are several work-arounds including using the new extension, the priority for v8 at this point in the release is not that high.

Some of the issues not addressed by the extension:

Also, the implementation:

  • Uses Type.MakeGenericType which is not ideal for performance. It could use an open generic internal class to wrap that, at least when the <T> value was passed in.
  • Should switch to IHostedStartupService.Starting with fallback so it works with just IHostedService.Start.

@joperezr
Copy link
Member

@geeknoid If we already have the intention of moving the functionality down to the platform in the future, would it make sense to make the one in the extensions package "Experimental" in favor of a future move, or would it make more sense to keep as is and just mark as obsolete when we get to this work?

@geeknoid
Copy link
Member

We can't mark it experimental, since it would prevent us from depending on it from any other package, which makes it mostly useless.

What would be the motivation to move this to dotnet/runtime in the future? Would there be a performance benefit, usability benefit, etc?

@steveharter
Copy link
Member

What would be the motivation to move this to dotnet/runtime in the future? Would there be a performance benefit, usability benefit, etc?

It would be one less package to reference, and easily discovered through intellisense.

We could push for this to get added to dotnet/runtime in v8.

@steveharter
Copy link
Member

Closing; the implementation in the extensions repro is there and essentially what would be added to the runtime, and which could be added to the runtime in the future by re-implementing it with a different extension class or by using Type forwards if the behavior is the same.

Without the extension, it is easy to work around by using AddHostedService() with an implementation of the new IHostedLifecycleService.Starting() or the previous IHostedService.Start(); these workarounds are discussed in #43149.

@joperezr
Copy link
Member

joperezr commented Aug 2, 2023

I believe there were some use cases in dotnet/aspnetcore repo that could benefit from moving this down to dotnet/runtime (as aspnetcore can't depend on extensions due to circular dependencies), so if that's the case could we re-open this and just set the milestone to post-.NET 8?

@ghost ghost locked as resolved and limited conversation to collaborators Sep 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-suggestion Early API idea and discussion, it is NOT ready for implementation area-Extensions-DependencyInjection
Projects
None yet
Development

No branches or pull requests

6 participants