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

HostingAbstractionsHostExtensions RunAsync disposes without logging #72225

Open
snakefoot opened this issue Jul 14, 2022 · 4 comments
Open

HostingAbstractionsHostExtensions RunAsync disposes without logging #72225

snakefoot opened this issue Jul 14, 2022 · 4 comments

Comments

@snakefoot
Copy link

snakefoot commented Jul 14, 2022

Description

The RunAsync in HostingAbstractionsHostExtensions.cs disposes the Host on exit:

public static async Task RunAsync(this IHost host, CancellationToken token = default)
{
try
{
await host.StartAsync(token).ConfigureAwait(false);
await host.WaitForShutdownAsync(token).ConfigureAwait(false);
}
finally
{
if (host is IAsyncDisposable asyncDisposable)

This disposes the ServiceProvider and the LoggerFactory, so unable to perform any logging after this point.

Inside the Host StartAsync then it nicely logs Starting, but any exception happing in the following code is not caught and logged:

public async Task StartAsync(CancellationToken cancellationToken = default)
{
_logger.Starting();
using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
CancellationToken combinedCancellationToken = combinedCancellationTokenSource.Token;
await _hostLifetime.WaitForStartAsync(combinedCancellationToken).ConfigureAwait(false);
combinedCancellationToken.ThrowIfCancellationRequested();
_hostedServices = Services.GetRequiredService<IEnumerable<IHostedService>>();

And because of the dispose, then any logging is impossible on exception:

Reproduction Steps

        var builder = Host.CreateDefaultBuilder();
        builder.ConfigureServices(s => s.AddHostedService<BrokenHostedService>());
        builder.Build().Run();

        class BrokenHostedService : IHostedService
        {
            public Task StartAsync(CancellationToken cancellationToken)
            {
                throw new Exception("Hosted service startup error!");
            }

            public Task StopAsync(CancellationToken cancellationToken)
            {
                return Task.CompletedTask;
            }
        }

Expected behavior

Exception is logged to the Microsoft Console Provider.

Actual behavior

Application crashes without any trace.

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jul 14, 2022
@ghost
Copy link

ghost commented Jul 14, 2022

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

Issue Details

Description

The RunAsync in HostingAbstractionsHostExtensions.cs disposes the Host on exit:

public static async Task RunAsync(this IHost host, CancellationToken token = default)
{
try
{
await host.StartAsync(token).ConfigureAwait(false);
await host.WaitForShutdownAsync(token).ConfigureAwait(false);
}
finally
{
if (host is IAsyncDisposable asyncDisposable)

This disposes the ServiceProvider and the LoggerFactory, so unable to perform any logging after this point.

Inside the Host StartAsync then it nice logs Starting, but any exception happing in the code is not caught and logged:

public async Task StartAsync(CancellationToken cancellationToken = default)
{
_logger.Starting();

And because of the dispose, then any logging is impossible.

Reproduction Steps

        var builder = Host.CreateDefaultBuilder();
        builder.ConfigureServices(s => s.AddHostedService<BrokenHostedService>());
        builder.Build().Run();

        class BrokenHostedService : IHostedService
        {
            public Task StartAsync(CancellationToken cancellationToken)
            {
                throw new Exception("Hosted service startup error!");
            }

            public Task StopAsync(CancellationToken cancellationToken)
            {
                return Task.CompletedTask;
            }
        }

Expected behavior

Exception is logged to the Microsoft Console Provider.

Actual behavior

Application crashes without any trace.

Regression?

No response

Known Workarounds

No response

Configuration

No response

Other information

No response

Author: snakefoot
Assignees: -
Labels:

area-Extensions-Hosting

Milestone: -

@eerhardt
Copy link
Member

The application crashes because an unhandled exception is being thrown and killing the Main thread. StartAsync is called directly on the Main thread, and until you await a Task it continues on the Main thread. See #36063 for more info on that.

You can workaround this by wrapping your StartAsync method in a Task.Run, which will execute your code on a background thread and not block the Main method of the app. However, if you throw an unhandled exception from that background thread, your app won't crash and nothing will be logged unless you log it yourself. See #36017. If you want Hosting to handle the logging of unhandled exceptions, you need to inherit from BackgroundService.

@eerhardt eerhardt added this to the Future milestone Jul 15, 2022
@eerhardt eerhardt removed the untriaged New issue has not been triaged by the area owner label Jul 15, 2022
@snakefoot
Copy link
Author

snakefoot commented Jul 16, 2022

Yes I'm aware that with enough effort, then one can add try-catches everywhere in user-code to ensure exception is logged.

But since having the Host-wrapper, that already now performs logging of Host-"Starting" and Host-"Stopped", then to me it would be natural that it also performed logging of Host-"Startup Error" on exception. Thus making the user-code cleaner with more focus on the success path, than the error-path.

@kmcclellan
Copy link

I also find it odd that exceptions thrown starting hosted services aren't logged while stopping exceptions are.

I include this boilerplate in my apps as a workaround. Definitely feels less idiomatic than host.RunAsync().

await using ((IAsyncDisposable)(host = builder.Build()))
{
    var logger = host.Services.GetService<ILogger<Program>>();

    try
    {
        await host.StartAsync();
    }
    catch (Exception exception)
    {
        logger?.LogCritical(exception, "Failed to start host");
        throw;
    }

    await host.WaitForShutdownAsync();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants