Skip to content
This repository has been archived by the owner. It is now read-only.

Generic Host #1163

Closed
davidfowl opened this issue Aug 11, 2017 · 50 comments

Comments

@davidfowl
Copy link
Member

commented Aug 11, 2017

Generic Host

As part of ASP.NET Core, we introduced a set of cross cutting concerns that are fundamental to building many applications.
These include things like configuration, dependency injection, and logging. The WebHostBuilder is the glue that brings those common cross cutting
concerns together but it also couples the building of the HTTP pipeline into the same component. The goal of the generic host is to de-couple
the HTTP pipeline building from the Host API to enable other scenarios like messaging, background tasks, and supporting other non HTTP workloads that would benefit
from the same cross-cutting libraries that HTTP benefits from.

Introducing Microsoft.Extensions.Hosting

Microsoft.Extensions.Hosting is the generic hosting library that will offer a similar API to WebHostBuilder but won't be specific to HTTP.
The eventual goal is to completely replace the WebHostBuilder API with this new one. The generic host will offer a few building blocks
for application models:

IHostedService

public interface IHostedService
{
    Task StartAsync(CancellationToken cancellationToken);

    Task StopAsync(CancellationToken cancellationToken);
}

This is the entry point to code execution. Many of these may be registered in as services. They will be resolved and executed in order of registration. StartAsync will be called when the host starts, and StopAsync will be called in reverse registration order when the host shuts down gracefully. IHostedServices are registered as part of container registration and resolved by the host as an IEnumerable<IHostedService>.

IHostBuilder

public interface IHostBuilder 
{
    IHost Build();
    
    IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate);

    IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate);
    
    IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureServices);

    IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureContainer);

    IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
    
    IDictionary<object, object> Properties { get; }
}

This is the main component that libraries and applications will extend to light up functionality.

IHost

public interface IHost : IDisposable
{
   IServiceProvider Services { get; }
   Task StartAsync(CancellationToken cancellationToken = default(CancellationToken));
   Task StopAsync(CancellationToken cancellationToken = default(CancellationToken));
}

The IHost implementation is responsible for starting and stopping all the IHostedServices that have been registered in DI.

There will also be a set of default services offered by the hosting environment:

IHostingEnvironment

public interface IHostingEnvironment
{
    string EnvironmentName { get; set; }
    
    string ApplicationName { get; set; }

    string ContentRootPath { get; set; }

    IFileProvider ContentRootFileProvider { get; set; }
    
    IConfiguration Configuration { get; }
}

The IHostingEnvironment provides information about the hosting environment an application is running in.

IApplicationLifetime

public interface IApplicationLifetime
{
    CancellationToken ApplicationStarted { get; }

    CancellationToken ApplicationStopping { get; }

    CancellationToken ApplicationStopped { get; }

    void StopApplication();
}

The IApplicationLifetime is a service that allows the application to handle host lifetime events and also request graceful shutdown of the host.

ILoggerFactory

https://github.com/aspnet/Logging

IConfiguration

https://github.com/aspnet/Configuration

IOptions<T>

https://github.com/aspnet/Options

Extensibliltiy

Host extensibility is done via extension methods on the IHostBuilder. The builder provides the ability to manipulate the core primitives and the
expectation is that extension methods can use this to build higher level functionality:

// UseRabbitMq is an extension method that registers a rabbit MQ 
// IHostedService and handles incoming messages.
var host = new HostBuilder()
            .UseRabbitMq<MyMessageHandler>() 
            .Build();
            
await host.StartAsync();

DependencyInjection

In order to support plugging in other containers, the host can accept an IServiceProviderFactory. This itself will not be part of the DI container registration, but will be a host intrinsic used to create the concrete DI container.

Custom container configuration can be done via the ConfigureContainer method. This gives you a strongly typed experience for configuring your container on top of the underlying API.

Host Lifetime

In order to accomodate systems that control the lifetime of the host, we introduce an new interface Lifetime to delegate lifetime management to another system for e.g. windows service host or console host.

public interface IHostLifetime
{
    void OnStarted(Action<object> callback, object state);
    void OnStopping(Action<object> callback, object state);
    Task StopAsync();
}

When IHost.StartAsync is called, it will register with the IHostLifetime to notify the caller when the application started or stopped via OnStarted and OnStopping. When IHost.StopAsync is called, the host will notify the IHostLifetime that it has stopped.

Here's an example implementation of the ConsoleHostLifetime that triggers shutdown on Control + C:

public class ConsoleLifetime : IHostLifetime
{
    public void OnStarted(Action<object> callback, object state)
    {
        // There's no event to wait on started
        callback(state);
    }

    public void OnStopping(Action<object> callback, object state)
    {
        Console.CancelKeyPress += (sender, e) =>
        {
            e.Cancel = true;
            callback(state);
        };
    }

    public Task StopAsync()
    {
        // There's nothing to do here
        return Task.CompletedTask;
    }
}

Startup

A note about Startup classes - While the concept of a Startup class is extremely useful, it's also very problematic as it requires
2 dependency injection containers to be built during the startup cycle. This causes issues where singletons aren't the same when activating the
Startup class and when activating an IHostedService. This is because Startup.ConfigureServices lets the user add more services but also lets them
get at hosting services in the Startup.ctor. So far, we've chosen to abandon startup as part of the generic host work but it would be possible to add back
at any point in the future if we figure out a cleaner way to do it.

Open Questions

  • We only expose a Start and Stop event but other hosts might have more advanced events they want to surface. Windows store has suspend/resume, windows services can be paused and continued as well. Do we need to plan for expanding the life cycle or is that out of scope?
  • How do we gradually migrate people over to this new pattern from WebHostBuilder?
  • Do we need to support ordering of IHostedServices? What if people want to write components that run after servers have started?
  • Do we need a first class server component that runs after hosted services (related to the previous bullet)?
  • The IServiceProviderFactory<TContainer> interface being generic might cause problems (no way to flow the generic through the system). We might need a non-generic version /cc @pakrym

@davidfowl davidfowl self-assigned this Aug 11, 2017

@davidfowl davidfowl added this to the 2.1.0 milestone Aug 11, 2017

@davidfowl

This comment has been minimized.

Copy link
Member Author

commented Aug 11, 2017

@nwoolls

This comment has been minimized.

Copy link

commented Aug 11, 2017

Are IHostBuilder.ConfigureAppConfiguration() and IHostBuilder.ConfigureAppConfiguration() meant to return IWebHostBuilder?

@davidfowl

This comment has been minimized.

Copy link
Member Author

commented Aug 11, 2017

Fixed! Thanks @nwoolls

@ReubenBond

This comment has been minimized.

Copy link

commented Aug 11, 2017

Thanks for opening the discussion, @davidfowl. @jdom pointed me here.

Glad that Startup class is gone. If a user wants to bring their own container, how do they do this using this new abstraction? I'm currently prototyping SiloBuilder for Orleans 2.0. This is what I have so far:

public interface ISiloBuilder
{
    ISilo Build();

    ISiloBuilder ConfigureServices(Action<IServiceCollection> configureServices);

    ISiloBuilder ConfigureServiceProvider(
        Func<IServiceCollection, IServiceProvider> configureServiceProvider);
}

ConfigureServiceProvider can be used to modify the services collection one last time and build the container.

Can IHost signal that it has terminated on its own accord? Self-termination is fairly common in some kinds of distributed systems where a node might decide to terminate for safety/correctness purposes. The typical pattern is that the hosting environment would restart it. This is my current equivalent to IHost:

public interface ISilo
{
    Task StartAsync(CancellationToken cancellationToken = default(CancellationToken));

    // If the cancellation token is already canceled, the silo will terminate ungracefully.
    // Otherwise it will perform a graceful shutdown, transfering its responsibilities to
    // other silos.
    Task StopAsync(CancellationToken cancellationToken = default(CancellationToken));

    IServiceProvider Services { get; }

    // The silo can kill itself and signal termination via this task.
    Task Stopped { get; }
}

In the event that an IHostedService terminates out-of-band, I expect that all others are stopped. Does that sound right?

If an IHostedService wants to receive notification that it should start graceful shutdown, is registering with IApplicationLifetime the mechanism for that?

Is IHostBuilder.Properties == IHostingEnvironment.Properties? If not, what's the difference?

@davidfowl

This comment has been minimized.

Copy link
Member Author

commented Aug 11, 2017

ConfigureServiceProvider can be used to modify the services collection one last time and build the container.

This looks like our IServiceProviderFactory concept. https://github.com/aspnet/DependencyInjection/blob/dev/src/DI.Abstractions/IServiceProviderFactory.cs. Thanks for the reminder, this is important for replacing the DI container.

Can IHost signal that it has terminated on its own accord? Self-termination is fairly common in some kinds of distributed systems where a node might decide to terminate for safety/correctness purposes. The typical pattern is that the hosting environment would restart it. This is my current equivalent to IHost:

I believe the equivalent is IApplicationLifetime.

In the event that an IHostedService terminates out-of-band, I expect that all others are stopped. Does that sound right?

Nope. The IHostedService would need to shutdown the host to terminate the other IHostedServices via IApplicationLifetime.StopApplication.

If an IHostedService wants to receive notification that it should start graceful shutdown, is registering with IApplicationLifetime the mechanism for that?

No need. StopAsync will be called if the application shuts down in any way.

Is IHostBuilder.Properties == IHostingEnvironment.Properties? If not, what's the difference?

IHostBuilder.Properties just allows you to round trip state during the building of the host. One reason might by idempotency. You wrote an extension methods that performed some modificationto the host and you don't want it to happen multiple times with several calls. You can use the Properties to store state and check it to see if that modification happened.

IHostingEnvironment.Properties are just a grab bag of things the host wants to flow to the application. Sorta like "server variables" or "environment variables". Of course you could just use a first class service for that.

@jdom

This comment has been minimized.

Copy link
Contributor

commented Aug 11, 2017

This looks great, I think we can align with Orleans too to use this.

One thing that isn't very clear here is what is the replacement for the StartupType.Configure method, which executes after the service provider was created? Or is the plan that users will be required to register some built-in (via extension methods) or custom IHostedService that can access services from the container as soon as the host starts and then finish the service configuration part?

There was no metion of Microsoft.Extensions.Options. I assume it's because ASP.NET will reference it, but not the hosting abstraction?

@davidfowl

This comment has been minimized.

Copy link
Member Author

commented Aug 11, 2017

One thing that isn't very clear here is what is the replacement for the StartupType.Configure method, which executes after the service provider was created? Or is the plan that users will be required to register some built-in (via extension methods) or custom IHostedService that can access services from the container as soon as the host starts and then finish the service configuration part?

IHostedService is the entry point. As an example, if we had this before WebHostBuilder, there would be a WebServiceHostedService that on Start, calling to Startup and then starts the server.

There was no metion of Microsoft.Extensions.Options. I assume it's because ASP.NET will reference it, but not the hosting abstraction?

That's a good point. We had a discussion on what the bare minimum set of required things needed to being in the core hosting layer. Options came up but we decided that it wasn't strictly required. That of course isn't set in stone. I think it would be valuable to add it as we added logging as well (which you can make the same argument, isn't required).

@jason-bragg

This comment has been minimized.

Copy link

commented Aug 11, 2017

@davidfowl Just for consideration in regards to the lifetime management and how it interacts with hosted services, we (Orleans team) prototyped this some ourselves as we needed better control over the per server startup/shutdown process. This is especially relevant to us because our hosts (silos) are part of a cluster that is intended to function as a whole for distributed applications.

The pattern we've come up with (and are not 100% committed to yet) consists of an observable lifetime (which we called lifecycle) and a lifecycle observer, which hosted services (or other components) could act as. These interfaces are as follows:

public interface ILifecycleObservable<in TStage>
{
    IDisposable Subscribe(TStage stage, ILifecycleObserver observer);
}

public interface ILifecycleObserver
{
    Task OnStart(CancellationToken ct);
    Task OnStop(CancellationToken ct);
}

The stages of the lifecycle are started in order, and stopped in reverse order, providing a staged startup process that allows dependent systems to be started and coordinated (even across a cluster) before the host is considered 'active' and ready for external traffic.

Our ILifecycleObservable is notably similar to the IHostLifetime, so I strongly suspect we'll be able to use our lifecycle pattern in a host built using what's been described here, but, as a general pattern, the lifecycle we're tinkering with is not limited to host lifetime, we're also using it in our grain lifecycle management (and likely any other complex start/stop sequences).

The PR where we introduced this can be seen in dotnet/orleans#3270 should you want to take a deeper look.

I'm not suggesting you change the lifetime pattern suggested in this thread, as it suits us fine, only bringing this similar pattern to your attention for consideration as you refine the host design.

@pksorensen

This comment has been minimized.

Copy link

commented Aug 11, 2017

For my non http service fabric applications my startup looks like this

        private static void Main()
        {
            try
            {     
                var config = new ConfigurationBuilder()
                    .SetBasePath(Directory.GetCurrentDirectory())
                    .AddEnvironmentVariables(prefix: "ASPNETCORE_");

                

                using (var container = new FabricContainer())
                {
                    container.AddOptions();
                    container.UseConfiguration(config);
                    container.RegisterType<IHostingEnvironment>(new ContainerControlledLifetimeManager(),new InjectionFactory(c=>
                    {
                        var _options = new WebHostOptions(c.Resolve<IConfiguration>());
                        var contentRootPath = ResolveContentRootPath(_options.ContentRootPath, AppContext.BaseDirectory);
                        var hostingEnvironment = new HostingEnvironment();
                        hostingEnvironment.Initialize("Elastic", contentRootPath, _options);
                        return hostingEnvironment;
                    }));
                    container.RegisterType(typeof(ILogger<>), typeof(Logger<>),new ContainerControlledLifetimeManager());
                    container.ConfigureSerilogging(logConfiguration =>
                             logConfiguration.MinimumLevel.Debug()
                             .Enrich.FromLogContext()
                             .WriteTo.LiterateConsole(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message}{NewLine}{Exception}{NewLine}")
                             .WriteTo.ApplicationInsightsTraces("10e77ea7-1d38-40f7-901c-ef3c2e7d48ef", Serilog.Events.LogEventLevel.Information));

                  
                    container.WithStatelessService<ElasticService>("ElasticServiceType");

                    // Prevents this host process from terminating so services keep running.
                    Thread.Sleep(Timeout.Infinite);

                }
                 
               
            }
            catch (Exception e)
            {
                ServiceEventSource.Current.ServiceHostInitializationFailed(e.ToString());
                throw;
            }
        }

So to get rid of the self plumbing for the basic stuff, that a generic host would offer, would be nice.

as for

A note about Startup classes - While the concept of a Startup class is extremely useful, it's also very problematic as it requires
2 dependency injection containers to be built during the startup cycle. This causes issues where singletons aren't the same when activating the
Startup class and when activating an IHostedService

I think unity dependency injection has a clean way of doing this. Instead of having both a service collection and a service resolver, its all the same. Meaning as soon a type has been registered, it can be resolved right away without building a new serviceProvider. This could solve the stuff about building and registering in startup.

@davidfowl

This comment has been minimized.

Copy link
Member Author

commented Aug 11, 2017

So to get rid of the self plumbing for the basic stuff, that a generic host would offer, would be nice.

Nice!

I think unity dependency injection has a clean way of doing this. Instead of having both a service collection and a service resolver, its all the same. Meaning as soon a type has been registered, it can be resolved right away without building a new serviceProvider. This could solve the stuff about building and registering in startup.

Yea, that sounds like a way to solve it but we're not changing any of the dependency injection system as part of this. I'm also not a fan of having a mutable service provider. That can lead to inefficiencies and bugs.

@andrebires

This comment has been minimized.

Copy link

commented Aug 11, 2017

I work in a messaging platform that implements similar concepts for generic hosting. It currently runs both as Windows services and console applications. I will share the details of how it works and maybe can serve as inspiration in some way. This structure has been used for some years in a very large codebase, working very well for us until now.

First, we have a very basic primitive caller IWorker that defines a component that execute a background task that can be eventually stopped. The idea is a rip off from TPL Dataflow (which we rely deeply), where all blocks have a void Complete() method and a Task Completion property to allow awaiting the job to end. The job in this case is hot and starts right after the instantiation of the class. It may be a one-shot job that ends by itself after some time (like running database migrations) or it can be a loop job that ends only if stopped (like listening for connections). The job should considered finished only when the Execution task is completed and it may hold exceptions in case of any errors during the job.

interface IWorker
{
    void Stop();

    Task Execution { get; }
}

The Stop method is synchronous because it only sends a signal to the task to stop it (i.e. cancel a CancellationTokenSource). If there's any async task to be executed after the work is stopped, it should be executed in the task hold by the Execution property.

Then we have a IService interface that extends IWorker and allows the cold start of the job, adding the StartAsync method.

interface IService : IWorker
{
    Task StartAsync(CancellationToken cancellationToken);
}

The StartAsync method is asynchronous because we may need to execute some preparation tasks for the job that may fail to execute and compromise the initialization.

We have another primitive called IServiceContainer that holds and orchestrate the execution of multiple services. It is a service by itself and allows adding services in specific tiers.

public interface IServiceContainer : IService
{        
    IServiceContainer Add(IService service, int tier = 0);
} 

When you start the container, it start all contained services in the order defined by the tier value, from the lower value to the higher. Services in the same tier are started in the same time. The Execution of the container holds the execution of all child services. When the container is stopped, the higher tiers are stopped first, going to the lower tiers after that.

Finally, we the the IServiceActivator interface that defines a service for bootstrapping services.

public interface IServiceActivator : IService
{
    IDictionary<Type, IServiceProvider> ServiceProviders { get; }
    
    ICollection<string> ActivationGroups { get; }        
}

The ServiceProviders property holds the dependency injection containers for each activated service and the ActivationGroups, which acts like a filter for which services should be activated accordingly to the host configuration. Our activator implementation searchs for IService implementations in the host (Console / Windows Service) referenced assemblies decorated with the ActivateAttribute, using an IServiceContainer for holding the created instances.

[AttributeUsage(AttributeTargets.Class)]
public sealed class ActivateAttribute : Attribute
{    
    public Type RegistrableType { get; set; }

    public int Tier { get; set; }

    public string ActivationGroup { get; set; }
}

Each service is instantiated by its own container, which is created by the activator and populated by the IRegistrable instance defined in the RegistrableType property.

public interface IRegistrable
{
    void RegisterTo(Container container);
}

The Container class is just an System.IServiceProvider which also allows the registration of types. The activator instantiates a new container, passes it to the RegisterTo method and try resolves an IService instance after that.

We also have a lot of helper classes for this infrastructure, like a ServiceBase class which implements common patterns for services (like synchronized initialization - avoiding problems when there are multiple start/stop calls) and services for monitoring the health of services (automatically restarting failed services), but the basics are above.

@tuespetre

This comment has been minimized.

Copy link

commented Aug 11, 2017

ConsoleHostLifetime that triggers shutdown on Control + C

This is what I came for and you did not disappoint, @davidfowl. 😂

@davidfowl

This comment has been minimized.

Copy link
Member Author

commented Aug 11, 2017

@andrebires very cool! Do you have multiple DI containers in the mix? Why does the IServiceActivator have a Dictionary of Type -> ISeriviceProvider? How does that work?

@andrebires

This comment has been minimized.

Copy link

commented Aug 11, 2017

@davidfowl Yes, the service activator creates a DI container for each activated service and expose they throught the ServiceProviders property . Currently, this is exposed only for testing reasons - sometimes we need to retrieve an instance created in a child container for asserting some conditions - but we could have an IServiceActivator implementation that, instead of automatically discovering the services to be activated, it receives they throught this property, separating the service discovery of the activation. The activator initialize every service by resolving its type in its own container.

Also, our activator implementation receives in the constructor a root DI container and every created child DI container is linked to it, creating an DI hierarchy. If the service requires a type that is not available in its own container, it can be resolved by the root container. This is useful in some cases that we need to share some types between services. For instance, we have a connection listener service that creates "in process" transport connections to our platform, so the client connections produced by this listener should be consumed by other service.

@jthelin

This comment has been minimized.

Copy link

commented Aug 11, 2017

It is important for this spec to be clear about what ordering guarantees [or not] there are related to StartAsync / StopAsync call sequences. Specifically:

  1. Do calls to IHostedService.StartAsync happen sequentially, or in parallel? I am assuming sequential is the "default" [and much easier to reason about!], but is there also some way to have some services initialize in parallel with each other? [to expedite server startup times]
  2. Do calls to IHostedService.StopAsync occur in reverse order to the StartAsync calls? Every system I have ever written what uses similar server-hosting functionality had some notion of "layering" which should be built up "bottom-to-top" and torn down "top-to-bottom".
  3. What is the mechanism to define & ensure a specific sequential order of IHostedService.StartAsync calls so that hosted services are initialized / started in a pre-defined order? It looks like this should be the job of an IHostBuilder instance, but will there be a "standard" default implementation that preserves ordering?

This expands a general point that @jason-bragg was making above, drawing out specific experience from building the Orleans hosting mechanisms, but is also confirmed from experiences with several Java server systems I have built over the years.

@jthelin

This comment has been minimized.

Copy link

commented Aug 11, 2017

What is the purpose of the CancellationToken returned from IApplicationLifetime.Application* properties? Are they supposed to be like [old style] .NET Events that apps can register handlers for?

Is myApplicationLifetime.ApplicationStarted.IsCancellationRequested condition becoming true similar to a [logical] public event EventHandler ApplicationStarted triggering? [modulo differences between push-vs-pull notification, of course.]

I think I am mostly getting confused by the choice of types used here because IsCancellationRequested seems like a "negative" assertion while ApplicationStarted seems like a "positive" assertion, although both allow similar Register operations for various "callback handlers" of course.

Also, does CancellationToken.CanBeCancelled have any meaning here distinct from CancellationToken.IsCancellationRequested?

@jthelin

This comment has been minimized.

Copy link

commented Aug 11, 2017

Is the CancellationToken param passed to the IHostedService.[Start|Stop]Async methods a complication that is useful or needed?

Say if a hosted service gets a call to StopAsync and starts to shut itself down, and then the cancellation token fires, is it supposed to "reinitialize" itself back to working state, ie. undo any [partial] shutdown changes it might be half way through?

If a Stop operation is allowed to be cancelled, then seems like for any non-trivial host system there are so many race conditions and corner-cases that would have to be considered and coded for that I am not sure even Einsteins would get it right.

It might be useful to think of a state transition diagram for lifecycle stages, to help clarify some of the edge-cases. For example, is this the expectation:

  • StopAsync + Cancel => Running?
  • StartAsync + Cancel => Stopped?

Is there a concrete IRL scenario for cancelling a server Stop operation in mid-flight, and what are the expected semantics that hosted services should aim for when that happens?

@davidfowl

This comment has been minimized.

Copy link
Member Author

commented Aug 11, 2017

@jthelin

It is important for this spec to be clear about what ordering guarantees [or not] there are related to StartAsync / StopAsync call sequences. Specifically:

I'll update the spec with these details.

Do calls to IHostedService.StartAsync happen sequentially, or in parallel? I am assuming sequential is the "default" [and much easier to reason about!], but is there also some way to have some services initialize in parallel with each other? [to expedite server startup times]

Sequentially. Parallel startup could be a bug farm.

Do calls to IHostedService.StopAsync occur in reverse order to the StartAsync calls? Every system I have ever written what uses similar server-hosting functionality had some notion of "layering" which should be built up "bottom-to-top" and torn down "top-to-bottom".

We'll revisit this. Today I don't think we call stop in the reverse order.

What is the mechanism to define & ensure a specific sequential order of IHostedService.StartAsync calls so that hosted services are initialized / started in a pre-defined order? It looks like this should be the job of an IHostBuilder instance, but will there be a "standard" default implementation that preserves ordering?

Today the DI container ensures order. Calls to register the IHostedService are ordered and will be preserved.

What is the purpose of the CancellationToken returned from IApplicationLifetime.Application* properties? Are they supposed to be like [old style] .NET Events that apps can register handlers for?

Those are basically events. There were made CancellationTokens because it supports both callbacks and polling. Nothing is being cancelled, it's more of a better EventHandler.

I think I am mostly getting confused by the choice of types used here because IsCancellationRequested seems like a "negative" assertion while ApplicationStarted seems like a "positive" assertion, although both allow similar Register operations for various "callback handlers" of course.

Yep, ignore that in this case. It could also be Task. See this dotnet/corefx#16221.

Also, does CancellationToken.CanBeCancelled have any meaning here distinct from CancellationToken.IsCancellationRequested?

Yes, they are different but they don't apply here.

Is the CancellationToken param passed to the IHostedService.[Start|Stop]Async methods a complication that is useful or needed?

Yes, they are. CancellationTokens are cooperative, so if the caller tries to cancel start up or shutdown, the implementations should get a chance to react.

Say if a hosted service gets a call to StopAsync and starts to shut itself down, and then the cancellation token fires, is it supposed to "reinitialize" itself back to working state, ie. undo any [partial] shutdown changes it might be half way through?

No, the CancellationToken is basically a timeout. The host says, I'll give you this long time shutdown and passes a token to StopAsync. If the token fires, it's the implementation's job to stop trying to shut down gracefully and just forcefully stop instead.

@jason-bragg

This comment has been minimized.

Copy link

commented Aug 11, 2017

@davidfowl, @andrebires @jthelin
I agree with @jthelin that it is important that the spec/design be clear about startup/shutdown behaviors.

I am of the opinion that some sort of controlled (or staged) startup/shutdown is very useful, evidenced by both the need for this within Orleans, @andrebires system, and @jthelin's experiences.

As this sort of staged lifetime can be, IMO, introduced as a natural extension to the generic host, I'm not convinced it should be a first class requirement. I'd rather see a solid minimal host implementation in place earlier than waiting for one with every bell and whistle (like staged lifetime) users may want.

I do, however, hope this capability is considered sufficiently in the initial version to allow its introduction by service developers or as a future improvement to the host.

jdom added a commit to dotnet/orleans that referenced this issue Aug 17, 2017

Minimal SiloBuilder (#3302)
This is a minimal "silo builder". The purpose is to make it significantly easier to configure, create, and operate a silo than it is at present.

We hope to collaborate on and support the generic HostBuilder abstraction once it's ready: aspnet/Hosting#1163, but this lets us continue making progress in the meantime.

Sample usage:
```C#
public static async Task MainAsync(string[] args)
{
    var silo = new SiloBuilder().ConfigureLocalHostPrimarySilo().Build();

    await silo.StartAsync();

    Task.Run(async () =>
    {
        // Wait an arbitrary amount of time and then kill the silo.
        // This could also be waiting for console input or some other condition.
        await Task.Delay(TimeSpan.FromMinutes(2));
        await silo.StopAsync();
    }).Ignore();

    await silo.Stopped;
}
```

* Moved default service implementations into `DefaultSiloServices` static class so that the implementation can be shared by SiloBuilder and Silo (for backwards compatibility)
* Broke cyclic dependency between `Silo` construction and: `MembershipOracle`, `MembershipOracleData`, and `DeploymentLoadPublisher`.
* Added `ISilo` interface for interacting with silo instances:
* Added `ISiloBuilder` interface and `SiloBuilder` implementation for incrementally configuring and eventually constructing an `ISilo` instance.
* Added an awaitable `siloTerminationTask` to `Silo` which is signaled on termination. Currently this sits alongside the similarly purposed (but not awaitable) `SiloTerminatedEvent`, which is a `WaitHandle`
@Antaris

This comment has been minimized.

Copy link

commented Aug 24, 2017

@davidfowl

We only expose a Start and Stop event but other hosts might have more advanced events they want to surface. Windows store has suspend/resume, windows services can be paused and continued as well. Do we need to plan for expanding the life cycle or is that out of scope?

The HTTP context and server are both examples where you've introduced extensibility through the notion of features. If the new IHost was also extensible using the same/similar mechanism, other hosts/platforms could augment the host with support for things like Pause/Continue, etc.

That was you could introduce something like a ISuspendable, or something along those lines that allow you code to react if the host supports such scenarios?

In terms of migrating IWebHost to IHost and IWebHostBuilder to IHostBuilder, could it not simply be:

  • Initial release, IWebHost : IHost and IWebHostBuilder : IHostBuilder
  • Mark IWebHost and IWebHostBuilder as [Obselete]

Future release:

  • Remove IWebHost and IWebHostBuilder
@kijanawoodard

This comment has been minimized.

Copy link

commented Aug 24, 2017

This looks a lot like NServiceBus configuration. You might want to ping @andreasohlund and see what lessons they've learned. I think they're revamping their generic hosting approach.

@davidfowl

This comment has been minimized.

Copy link
Member Author

commented Aug 25, 2017

The HTTP context and server are both examples where you've introduced extensibility through the notion of features. If the new IHost was also extensible using the same/similar mechanism, other hosts/platforms could augment the host with support for things like Pause/Continue, etc.

Maybe, I'm not convinced yet though.

That was you could introduce something like a ISuspendable, or something along those lines that allow you code to react if the host supports such scenarios?

How do you imagine that being used and what would the impact of such changes be on the interface?

In terms of migrating IWebHost to IHost and IWebHostBuilder to IHostBuilder, could it not simply be:

The interfaces don't map very well (there are improvements we want to make) and this would be a breaking change.

Tratcher added a commit that referenced this issue Sep 14, 2017

@davidfowl

This comment has been minimized.

Copy link
Member Author

commented Sep 15, 2017

We have an implementation now. Thanks for the feedback! Please try it out and file issues.

@sirajmansour

This comment has been minimized.

Copy link

commented May 2, 2018

A note about Startup classes - While the concept of a Startup class is extremely useful, it's also very problematic as it requires
2 dependency injection containers to be built during the startup cycle. This causes issues where singletons aren't the same when activating the
Startup class and when activating an IHostedService. This is because Startup.ConfigureServices lets the user add more services but also lets them
get at hosting services in the Startup.ctor.

@davidfowl i am assuming this is why a singleton injected into my IHostedService is a different instance than the one resolve and configure somewhere else. Is there a work around that you can suggest ?

@guardrex

This comment has been minimized.

Copy link
Contributor

commented May 11, 2018

One more (possibly dumb) ? if you cats don't mind ...

Why wasn't it possible (or reasonable) to roll the host config and the app config together into ONE BUILDER? It seems like it would be simpler for devs if a single config builder handled both the host and the app config with one set of providers in one spot at one time. I see that if someone wanted that, they could do it themselves using the bits you've made available, but why wasn't it an option (or the default) OOB for ASP.NET Core?

@muratg

This comment has been minimized.

Copy link

commented May 11, 2018

@Tratcher / @davidfowl -- was it just an issue of seperation of concerns?

@poke

This comment has been minimized.

Copy link

commented May 12, 2018

My guess is that this is due to how the configuration evolved over time. Back in the days™ we had a hosting.json by default that was responsible of setting up hosting related configuration, and the configuration was completely separated with the application specific stuff. It only happened later that the hosting.json was removed, and you were supposed to do that yourself. And only with the new web host builder, hosting related stuff like logging or the 2.1 Kestrel configuration also moved into the appsettings.json, making a single configuration builder more appropriate.

@Tratcher

This comment has been minimized.

Copy link
Member

commented May 12, 2018

Some minimal amount of config is needed to bootstrap the host builder, IHostingEnvironment, etc.. That information is then used as input to the rest of the HostBuilder flow. E.g. configApp.AddJsonFile( $"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", wouldn't work with a single config stage, you don't know where to get EnvironmentName from.

@Tratcher

This comment has been minimized.

Copy link
Member

commented May 12, 2018

@guardrex note we made some updates to IHostLifetime after we had trouble getting it to work with windows services. See if the new version makes any more sense: https://github.com/aspnet/Hosting/blob/38f691c09e3aae4f34be139c39ae1fb264803fd0/src/Microsoft.Extensions.Hosting/Internal/ConsoleLifetime.cs

@jwfx

This comment has been minimized.

Copy link

commented Jun 11, 2018

@Tratcher
I'm just facing the problem that IApplicationLifetime.StopApplication() does not stop an application that is started as a Windows service (public class WindowsService : WebHostService). Currently using 2.0 bits.

Are these updates related to this issue and if yes, I suppose they are only available in 2.1?

@Tratcher

This comment has been minimized.

Copy link
Member

commented Jun 11, 2018

@jwfx that's not related to this issue, please open a new one and give the details for your scenario.

@kosa-gyula-77

This comment has been minimized.

Copy link

commented Nov 13, 2018

Re the issues where singletons aren't the same when activating the Startup class and when activating an IHostedService, are web host builders supposed to 1) switch to building their host with generic host or 2) just upgrade their use of aspnet/Hosting to version 2.1.0+ , please?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.