-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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: New overload for ServiceController.Stop() #35284
Comments
@Fs00 can you modify the description to fill this template described here https://github.com/dotnet/runtime/blob/master/docs/project/api-review-process.md You can mention both the implementation details in the descripition. |
@Fs00 we actually have an issue template for API proposals now, if you like you can use that, but instead of opening a new issue, paste into the top post of this one. |
@Anipik @danmosemsft Just updated the first comment with the template you linked. Let me know if you would like to see some other changes. |
@Anipik is this a must have to ship 5.0? My reading is that this should be in the Future milestone. If it get into 5.0, fine. |
that sounds good to me. |
@Anipik Can this be marked as ready for API review? |
For UI scenarios, it might be useful to have
The current 30-second timeout applies to each dependent service individually, so the total time spent stopping all dependent services can be much longer. Would |
If overloads with CancellationToken are ever added, which doesn't have to happen as part of this issue, then I think the least surprising API would have four Stop methods: namespace System.ServiceProcess
{
public class ServiceController : Component
{
public void Stop();
+ public void Stop(TimeSpan dependentServicesStopTimeout);
+ public void Stop(CancellationToken cancellationToken);
+ public void Stop(TimeSpan dependentServicesStopTimeout, CancellationToken cancellationToken);
}
} TimeSpan dependentServicesStopTimeout would apply to each dependent service and default to 30 seconds. Exceeding it would cause TimeoutException. Timeout.InfiniteTimeSpan would remove the limit. CancellationToken cancellationToken would apply to the whole tree of services and default to CancellationToken.None. Exceeding it would cause OperationCanceledException. |
@KalleOlaviNiemitalo You brought up several interesting points, I'll try to answer them one by one.
Yes, it would be treated in the same way. And yes, its name is not perfect but I find it hard to find a good alternative (maybe something like Also, I really like the idea of the CancellationToken overload because it gives more flexibility, even though it might seem a bit overkill for this API imho.
I think that .NET team should choose only one of the two approaches, otherwise the API would quickly become confusing. What if we replace my alternative design in the proposal (which is not very interesting anyway) with your CancellationToken overload? It seems to me that both ideas aim to solve very similar problems and that it makes sense to have only one of the two. |
An alternative design could be:
And the bool return value would indicate if a timeout occurred. A caller interested in a timeout could first call |
@Anipik Sorry for bothering you again, but I find the CancellationToken overload proposed by @KalleOlaviNiemitalo a very interesting alternative design for this proposal. |
I think it would be confusing if both |
I understood that they would be equivalent in your proposal, or did I miss something? |
Oh, I thought you were only going to propose adding |
My idea is to propose |
no worries, let me know when u r ready |
@Fs00, do you mean
I am a bit worried about what would happen after the OperationCanceledException. Stop(CancellationToken) may already have stopped some services but the app does not know which ones those were. Although this concern applies to Stop() with TimeoutException as well, it is less serious there because it only happens if a service is misbehaving. I suppose that, if the user is trying to cancel the operation and close the app, then neither Stop(CancellationToken) nor the app should try to restart those services automatically. If the names of the stopped services (and the service that ServiceController last asked to stop) were reported back to the application (e.g. in Exception.Message, or via a new parameter of Stop), then the app could at least log them. However, if it is an interactive app, then perhaps nobody would read the log anyway; and if the app is not interactive, then I guess it won't provide a CancellationToken in the first place. Perhaps then, it is best not to attempt to solve that; an app that really cares can stop the dependent services itself and have full control. If you're adding |
Oh, it looks like the current implementation of ServiceController.WaitForStatus treats Timeout.InfiniteTimeSpan as negative rather than infinite, and throws TimeoutException after checking the status just once. Lines 893 to 910 in cf258a1
Making it treat Timeout.InfiniteTimeSpan as infinite might now be a breaking change, and TimeSpan.MaxValue can already be used as a workaround. |
This is a very reasonable concern, I agree with you on this.
Full control. I think that's the main point of all this. The problem is that the Stop() method as it is now tries to stop all dependent services and there is no way to prevent or control this behavior. You surely can stop all dependent services by yourself (with the ability to choose your custom timeout) before calling Stop(), but the latter will always try to stop dependents even you have already stopped them. Regarding my originally proposed overload, public void Stop(bool stopDependentServices) This overload would be called by those users that want to stop dependent services by themselves (passing in What do you think? @KalleOlaviNiemitalo PS: |
Yes, If ControlService SERVICE_CONTROL_STOP fails, then the current ServiceController.Stop() implementation throws InvalidOperationException with an inner Win32Exception. ServiceController.Stop(bool) would presumably do the same. So, if an application first stopped dependent services and then called ServiceController.Stop(false), but one of the dependent services had been restarted (e.g. by a service trigger event) in the meantime, then the application would be able to detect this specific case by checking for ERROR_DEPENDENT_SERVICES_RUNNING in the Win32Exception. |
Mainly because ServiceController.WaitForStatus might eventually be changed to use NotifyServiceStatusChangeW instead of polling every 250 milliseconds, as planned in https://github.com/dotnet/corefx/pull/627/files#r24206059. I don't think there currently is a GitHub issue for that feature request, though. |
Ok, I will replace my proposal with
|
@Anipik Proposal updated. If you're ok with it, this can be marked as ready for review. |
namespace System.ServiceProcess
{
public partial class ServiceController
{
public void Stop(bool stopDependentServices)
}
} |
Is anyone interested in offering an implementation+tests? |
@danmosemsft Count me in! 😃 |
It's assigned to you @Fs00 .. reach out here if you have questions. thanks! |
Background and Motivation
Follow-up of #765.
The Stop method of the ServiceController class behaves slightly differently when a service has some dependent services: first of all, it stops the latter ones and waits up to 30 seconds for them to be stopped.
This behavior can not be controlled nor disabled by the user. This poses a problem when the user wants to stop dependent services using their custom logic, which could simply consist in a different waiting timeout like in the example below:
The call to
serviceController.Stop()
in the last line will still iterate through the dependent services, checking if they are stopped and stopping them if they're not. This is undesirable for two reasons:serviceController.Stop
? ThenserviceController.Stop
would try to stop that service again with the default timeout of 30 seconds. This can be confusing, but more importantly, not wanted by users who prefer to do that manually.Proposed API
namespace System.ServiceProcess { public class ServiceController : Component { + public void Stop(bool stopDependentServices) } }
When the
stopDependentServices
parameter is set to false, there will be no attempt to stop or check the status of dependent services. In case the service has one or more dependents that are running,Stop(false)
will throw an InvalidOperationException with an inner Win32Exception with error codeERROR_DEPENDENT_SERVICES_RUNNING
(the current implementation may already do this).The pre-existing Stop() parameterless overload will delegate to the new one, passing in the value
true
.Usage Examples
The example shown above can take advantage of this new overload in the following way:
Or better, a user can now create a recursive method that stops a service and its whole dependents tree with a custom timeout:
Alternative Design
We could avoid the boolean parameter flag in the public API (which is not best for design and readability) by making the
Stop(bool)
overload private and instead exposing a parameterless method that delegates toStop(false)
.The thing is, I'm not sure on how would I call such method.
StopServiceOnly
is the only name that came to my mind, but it still wouldn't be as descriptive as invoking theStop(bool)
overload specifying the name of the parameter, such asStop(stopDependentServices: false)
.I'm open to any suggestion on this.
Risks
None I think, since the original behavior is preserved and the user must opt-in to customize it.
The text was updated successfully, but these errors were encountered: