In-depth: How .NET managed services interact with the ServiceControlManager (SCM) [Kim Hamilton]

.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.

SCM Interaction 1

General Guidance

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

Background

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.

Impact for managed services

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:

  • You don’t need to start a new thread just to unblock the SCM. ServiceBase calls your OnStart/OnStop (we’ll refer to those as OnX) on a worker thread, so when you’re OnStart/OnStop is called, you’re not blocking the SCM
  • You don’t set the service state. The ServiceBase class sets the service state to X_PENDING before calling your OnX/OnX. Also, when your OnX method returns, ServiceBase performs the final status update, e.g. to STATUS_STOPPED. (In fact, there’s not even a managed API for setting these states.)
  • While your OnX thread isn’t blocking the SCM, you do need ensure it doesn’t exceed its time limit. You can help ensure you have enough time by calling RequestAdditionalTime, discussed in SCM Interaction 2.
  • You do need to spawn a new thread in OnStart for any processing that should happen during the entire lifetime of the service.

SCM Interaction 2

General Guidance

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.

Background

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.

Impact for managed services

This is where your managed service needs to pay attention to avoid the SCM flagging your service as unresponsive.

  • You don’t have to explicitly update the state; ServiceBase does this for you when your OnStart or OnStop method completes
  • You do have to call RequestAdditionalTime if you expect the OnX method to exceed the timeout.

SCM Interaction 3

General Guidance

To give SCM impression of progress, update the dwCurrentState or dwCheckPoint

Background

These state and checkpoints are intermediate “in progress” types of notification to let the SCM know your service isn’t hanging.

Impact for managed services

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.

Walkthrough: how ServiceBase handles Stop command

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:

  • ServiceBase queues up an async call, to call your handler and update status
  • In the async call, ServiceBase does the following:
    • Sets status to STATE_STOP_PENDING
    • Calls your OnStop (note this is already in a separate thread)
    • When your OnStop returns, sets state to STATE_STOPPED

What should your OnStop look like?

  • If you need more time, just call RequestAdditionalTime. We’ll send this value to the SCM and update the checkpoint for you
  • If you don’t need more time, you don’t have to do any additional steps. Your method returns and we update the state for you.

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)

Additional Reading

The following MSDN article describes writing responsive native services.

https://msdn.microsoft.com/en-us/magazine/cc164252.aspx