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

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

  • Comments 9

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

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

  • Hi Kevin,

    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.

    Thanks,

    Kim

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

Page 1 of 1 (9 items)