dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.37k stars 4.75k forks source link

Subsequent hostedServices are not getting started with StartAsync #78135

Closed aaruhya closed 1 year ago

aaruhya commented 2 years ago

When the host.StartAsync is run, the loop on the hostedServices (BackgroundService) does not finish when one of the hosted services -> ExecuteAsync awaits on a call that has a while loop in it.

The hostedServices are of type BackgroundService. BackgroundService -> StartAsync calls ExecuteAsync on the worker.

protected override async Task ExecuteAsync(CancellationToken cancellationToken)
        {
            await _kafkaConsumer.Consume(_messageHandler.Consume, cancellationToken);
        }

The Consume function has a while loop

while (true)
                {
                    var consumerResult = _timeoutPolicy.Execute((ctx, ct) => consumer.Consume(pollInterval), _timeoutPolicyContext, cancellationToken);
                    if (consumerResult == null || consumerResult.IsPartitionEOF) continue;

                    _messageTimestamp?.Invoke(consumerResult.Message.Timestamp);
                    try
                    {
                        await messageHandler(consumerResult.Message.Value, cancellationToken);

                    }
......

In Host.StartAsync(), there is a loop over the hosted services that calls StartAsync on the services.

foreach (IHostedService hostedService in _hostedServices)
            {
                // Fire IHostedService.Start
                await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
            }

Once the above Background service is started, the loops stops and the subsequent services are not started.

ghost commented 2 years ago

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

Issue Details
When the host.StartAsync is run, the loop on the hostedServices (BackgroundService) does not finish when one of the hosted services -> ExecuteAsync awaits on a call that has a while loop in it. The hostedServices are of type BackgroundService. BackgroundService -> StartAsync calls ExecuteAsync on the worker. ``` protected override async Task ExecuteAsync(CancellationToken cancellationToken) { await _kafkaConsumer.Consume(_messageHandler.Consume, cancellationToken); } ``` The Consume function has a while loop ``` while (true) { var consumerResult = _timeoutPolicy.Execute((ctx, ct) => consumer.Consume(pollInterval), _timeoutPolicyContext, cancellationToken); if (consumerResult == null || consumerResult.IsPartitionEOF) continue; _messageTimestamp?.Invoke(consumerResult.Message.Timestamp); try { await messageHandler(consumerResult.Message.Value, cancellationToken); } ...... ``` In Host.StartAsync(), there is a loop over the hosted services that calls StartAsync on the services. ``` foreach (IHostedService hostedService in _hostedServices) { // Fire IHostedService.Start await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false); } ``` Once the above Background service is started, the loops stops and the subsequent services are not started.
Author: aaruhya
Assignees: -
Labels: `area-Extensions-Hosting`
Milestone: -
ericstj commented 1 year ago

Behavior is to call StartAsync sequentially for all services and they are expected to not block the thread calling StartAsync. This is implemented here: https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Hosting.Abstractions/src/BackgroundService.cs#L46-L55

That will call ExecuteAsync method, but capture the task and return context to the caller. For that to work correctly your async method needs to do minimal work in the synchronous portion of the method and return a task.

My best guess at your bug here is that your async methods are actually not yielding the thread. In the example shown, if everything that's awaited is just doing work and never yielding the thread you are effectively stuck in that loop as you're always executing the first synchronous portion of the async state machine.

You can fix that by explicitly yielding the thread (with Task.Run() in your Execute method, for example) or calling some other "real" asynchronous API (like networking, IO, etc). Probably whoever implements _kafkaConsumer.Consume might want to consider if there is an opportunity to introduce some real async. Perhaps in _timeoutPolicy.Execute.

ghost commented 1 year ago

This issue has been marked needs-author-action and may be missing some important information.

ghost commented 1 year ago

This issue has been automatically marked no-recent-activity because it has not had any activity for 14 days. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will remove no-recent-activity.

ghost commented 1 year ago

This issue will now be closed since it had been marked no-recent-activity but received no further activity in the past 14 days. It is still possible to reopen or comment on the issue, but please note that the issue will be locked if it remains inactive for another 30 days.