Base types, Collections, Diagnostics, IO, RegEx…
.NET managed services are based on NT services, and both need to know how to interact with the ServiceControlManager (SCM) to remain responsive. For managed services, many of the complexities of interacting with the SCM are handled for you by System.ServiceProcess.ServiceBase; however, there are some you still need to be aware of.
I’ll summarize the key interactions between the SCM and any service (managed or native). I’ll describe how these apply to managed services — which interactions you need to perform and which are handled for you. Lastly, I’ll walk through a representative SCM command to show the full sequence.
When SCM sends a request, acknowledge it by updating the service state (e.g. pending) and spawn a new thread to handle the request so that the SCM can communicate with other services
The SCM only communicates with one service at a time. This guidance allows the SCM to be unblocked so it can communicate with other services. So the suggested behavior for services is: set the status to pending and kick off a new thread to do any necessary work (thereby unblocking the SCM). The SCM can then move on to interacting with other services, while the worker thread updates the SCM with progress and completion status.
This is only partially relevant for managed services because ServiceBase handles much of this for you. You only need to worry about this if you expect your service will require more time than the “unresponsive” limit, or to spawn a thread that will last the entire lifetime of the service. Here are the details:
After the SCM issues a command, the SCM gives each service a default amount of time to complete its work. If the service requires more time it must tell the manager that it needs more time, otherwise the manager will assume it’s hung.
As mentioned in the previous interaction, it’s important that a service doesn’t block the SCM, so that the SCM can respond to other services. However, the SCM also expects status updates from the service, to ensure the service is responsive. For example, after issuing a start command, the SCM expects the service to update its state to started within 30 seconds. After issuing a stop command, the SCM expects the service to update its state to stopped within 20 seconds.
This is where your managed service needs to pay attention to avoid the SCM flagging your service as unresponsive.
To give SCM impression of progress, update the dwCurrentState or dwCheckPoint
These state and checkpoints are intermediate “in progress” types of notification to let the SCM know your service isn’t hanging.
With managed services, these details are abstracted away. All you have to do is call RequestAdditionalTime() and those details are handled for you behind the scenes. ServiceBase will handle the state and checkpoint details in its message to the SCM.
To demonstrate the above, let’s walk through how ServiceBase responds to a STOP command from the SCM. Note that Start is similar.
ServiceBase receives STOP command:
What should your OnStop look like?
If your OnStop fails to call RequestAdditionalTime or blocks for longer than 20 (default; see below) seconds, SCM marks service as unresponsive.
To prevent a service from stopping shutdown, the SCM will only wait up to a limit for your service to stop itself. The default for this limit is 20 seconds (this value is in the registry key WaitToKillServiceTimeout() in HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control)
The following MSDN article describes writing responsive native services.
http://msdn.microsoft.com/en-us/magazine/cc164252.aspx
PingBack from http://www.clickandsolve.com/?p=11673
Thanks so much for this post!
I've done many unmanaged services in the past, and only a couple managed ones. I suspected the OnStart/OnStop were coming from non-SCM threads but never took the time to check for sure.
-Steve
I've seen some strange behavior from managed services that I've never found an explanation for.
If the service starts up and runs through its full start-up sequence, it shuts down fine (OnStop is called). However, if I start it up, and during its startup process (during OnStart processing, status = ServiceStartPending) I try to stop the service, it seems to get in a funny state. The stop request fails with a "service could not be stop" message, and the service remains running. If I try to stop it again at this point, ServiceBase.Run() returns immediately, and OnStop is never called.
Can you offer any explanation for this, or how to handle to properly?
Hi Kevin,
ServiceBase won't call your OnStop if it's in the START_PENDING state (see below*). There are a couple of things you can do to mitigate this. Our general recommendation is to make your OnStart as fast as possible (i.e. launch another thread for any additional work that's not critical for startup). This reduces the time in the START_PENDING state, which is good in general because it reduces the chance that your service will get marked unresponsive (because your OnStart didn't finish in time).
I'd also suggest overriding Dispose(bool) -- don't forget to call base.Dispose(bool) as well -- and put any critical cleanup there.
*Side note: this is an interesting design decision that I can see both side of. It seems odd to filter out notifications; however, this avoids the need to worry about tricky state transitions that may not work reliably in general due to timing considerations.
Thanks,
Kim
<p>&#160;</p> ...
Kim,
Thanks for the response - it's nice to know the real answer.
Seems like a bit of strange design choice - if we need to spawn a separate thread anyway, what's the point of the framework running OnStart on a worker thread in the first place? If your startup takes long enough that you would need to call RequestAdditionalTime, you're almost guaranteed to be exposed to this scenario.
Anyway, thanks again for the info.
That's true. My guess is that this scenario (stopping the service while it's in the start_pending state) was seen as very corner case. Calling your OnStart on a separate thread, however, is essential for unblocking the SCM. Doing this makes service development very easy for mainstream cases (i.e. the SCM is already unblocked and we set the state to started for you when the thread returns). In that sense, this is just one example of the tradeoffs in managed class design -- i.e. making them easy to use versus allowing all the hooks native code provides.
Kim Hamilton has a couple of excellent posts on the BCL Team blog . In the first post on Working with
Please just allow us to do it like it were any other pointer in our code from VS.