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

Add the ability to start and stop IHostedService instances concurrently #75894

Conversation

jerryk414
Copy link
Contributor

Implements #68036

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost ghost added the community-contribution Indicates that the PR has been added by a community member label Sep 20, 2022
@ghost
Copy link

ghost commented Sep 20, 2022

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

Issue Details

Implements #68036

Author: jerryk414
Assignees: -
Labels:

new-api-needs-documentation, area-Extensions-Hosting

Milestone: -

@tarekgh tarekgh added this to the 8.0.0 milestone Sep 20, 2022
@jerryk414
Copy link
Contributor Author

jerryk414 commented Sep 21, 2022

I'm not sure why the check runtime (Libraries Test Run release coreclr Linux_musl x64 Debug) is failing - i've checked the logs and everything i'm seeing indicates that the actual tests are succeeding but some portion of the process to run the tests is failing.

It looks like it's failing on the Send to Helix step. I did find a helix log that indicates that the issue is unrelated to my changes but related to an issue with uploading helix logs.

Seems like this could be related to #74699.

Any assistance would be appreciated.

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution, @jerryk414!

@@ -17,6 +17,16 @@ public class HostOptions
/// </summary>
public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(30);

/// <summary>
/// Determines if the <see cref="IHost"/> will start registered instances of <see cref="IHostedService"/> concurrently or sequentially
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to document the default.

@@ -30,11 +40,25 @@ public class HostOptions
internal void Initialize(IConfiguration configuration)
{
var timeoutSeconds = configuration["shutdownTimeoutSeconds"];
if (!string.IsNullOrEmpty(timeoutSeconds)
if (!string.IsNullOrWhiteSpace(timeoutSeconds)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change seems unrelated/unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why you would want to use NullOrEmpty over NullOrWhiteSpace here. The TryParse methods further down the chain will handle the blank-space scenarios, but I don't see a need to even get there when we can stop it so easily.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The odds that the config value is going to be just whitespace are VERY low. So doing extra checking just slows down the happy path.

Look at the implementation of IsNullOrWhiteSpace:

public static bool IsNullOrWhiteSpace([NotNullWhen(false)] string? value)
{
if (value == null) return true;
for (int i = 0; i < value.Length; i++)
{
if (!char.IsWhiteSpace(value[i])) return false;
}
return true;
}

It goes through the string checking the chars one by one. There is no reason to do this.

&& int.TryParse(timeoutSeconds, NumberStyles.None, CultureInfo.InvariantCulture, out var seconds))
{
ShutdownTimeout = TimeSpan.FromSeconds(seconds);
}

var servicesStartConcurrently = configuration["servicesStartConcurrently"];
if (!string.IsNullOrWhiteSpace(servicesStartConcurrently)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!string.IsNullOrWhiteSpace(servicesStartConcurrently)
if (!string.IsNullOrEmpty(servicesStartConcurrently)

}

var servicesStopConcurrently = configuration["servicesStopConcurrently"];
if (!string.IsNullOrWhiteSpace(servicesStopConcurrently)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!string.IsNullOrWhiteSpace(servicesStopConcurrently)
if (!string.IsNullOrEmpty(servicesStopConcurrently)

Comment on lines +45 to +50
foreach (bool stopConcurrently in new[] { true, false })
foreach (bool startConcurrently in new[] { true, false })
foreach (int hostedServiceCount in new[] { 0, 1, 10 })
{
yield return new object[] { stopConcurrently, startConcurrently, hostedServiceCount };
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foreach (bool stopConcurrently in new[] { true, false })
foreach (bool startConcurrently in new[] { true, false })
foreach (int hostedServiceCount in new[] { 0, 1, 10 })
{
yield return new object[] { stopConcurrently, startConcurrently, hostedServiceCount };
}
foreach (bool stopConcurrently in new[] { true, false })
{
foreach (bool startConcurrently in new[] { true, false })
{
foreach (int hostedServiceCount in new[] { 0, 1, 10 })
{
yield return new object[] { stopConcurrently, startConcurrently, hostedServiceCount };
}
}
}

See rules 1 and 18 in https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md

{
foreach (IHostedService hostedService in _hostedServices.Reverse())
// Ensure hosted services are stopped in FILO order
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Ensure hosted services are stopped in FILO order
// Ensure hosted services are stopped in LIFO order

Comment on lines +231 to +237
_logger.HostedServiceStartupFaulted(exceptions[0]);
ExceptionDispatchInfo.Capture(exceptions[0]).Throw();
}
else
{
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
_logger.HostedServiceStartupFaulted(ex);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is stopping, not starting

Suggested change
_logger.HostedServiceStartupFaulted(exceptions[0]);
ExceptionDispatchInfo.Capture(exceptions[0]).Throw();
}
else
{
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
_logger.HostedServiceStartupFaulted(ex);
_logger.StoppedWithException(exceptions[0]);
ExceptionDispatchInfo.Capture(exceptions[0]).Throw();
}
else
{
var ex = new AggregateException("One or more hosted services failed to stop.", exceptions);
_logger.StoppedWithException(ex);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof good catch, i don't know how I missed that

}
catch (Exception ex)
{
exceptions.AddRange(tasks.Exception?.InnerExceptions?.ToArray() ?? new[] { ex });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
exceptions.AddRange(tasks.Exception?.InnerExceptions?.ToArray() ?? new[] { ex });
exceptions.AddRange(tasks.Exception?.InnerExceptions ?? new[] { ex });

There's no need to ToArray() here. Same comment for the stopping code.

IList<Exception> exceptions = new List<Exception>();
if (_hostedServices != null) // Started?
List<Exception> exceptions = new List<Exception>();
if (_hostedServices?.Any() == true) // Started?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the change to call Any() here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the inner block here might succeed even if the list is empty. I'm not 100% sure what happens if you pass an empty list of tasks into Task.WhenAll, so i'll have to figure that out.

I just felt like it wasn't necessary to even have to worry about that.

@maryamariyan
Copy link
Member

Thanks for the contribution @jerryk414 let me know if you have any blockers with addressing the remaining feedback.

@Nick-Stanton Nick-Stanton added the needs-author-action An issue or pull request that requires more info or actions from the author. label Oct 11, 2022
@ghost ghost added the no-recent-activity label Oct 26, 2022
@ghost
Copy link

ghost commented Oct 26, 2022

This pull request 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.

@tarekgh
Copy link
Member

tarekgh commented Nov 1, 2022

Closing it we can consider opening when we get more feedback.

@tarekgh tarekgh closed this Nov 1, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Dec 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-Extensions-Hosting community-contribution Indicates that the PR has been added by a community member needs-author-action An issue or pull request that requires more info or actions from the author. new-api-needs-documentation no-recent-activity
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants