Dec 13, 2011

How to increase service start/stop timeout ?

Windows provide SetServiceStatus function by which one can manage service time out period. It’s a very straight forward API here are its details:-
         BOOL WINAPI SetServiceStatus(
                            __in  SERVICE_STATUS_HANDLE hServiceStatus,
                            __in  LPSERVICE_STATUS lpServiceStatus

To manage timeout one need to inform SCM periodically about the current status of service. So if one have pending operations same information can be conveyed with this interface to SCM.
A common bug is for the service to have the main thread perform the initialization while a separate thread continues to call SetServiceStatus to prevent the service control manager from marking it as hung. However, if the main thread hangs, then the service start ends up in an infinite loop because the worker thread continues to report that the main thread is making progress.

Here are the details of structure

typedef struct _SERVICE_STATUS {
  DWORD dwServiceType;
  DWORD dwCurrentState;
  DWORD dwControlsAccepted;
  DWORD dwWin32ExitCode;
  DWORD dwServiceSpecificExitCode;
  DWORD dwCheckPoint;
  DWORD dwWaitHint;

And following are the main parameters to be used while managing service time out duration in the structure

The check-point value the service increments periodically to report its progress during a lengthy start, stop, pause, or continue operation. For example, the service should increment this value as it completes each step of its initialization when it is starting up. The user interface program that invoked the operation on the service uses this value to track the progress of the service during a lengthy operation. This value is not valid and should be zero when the service does not have a start, stop, pause, or continue operation pending.

The estimated time required for a pending start, stop, pause, or continue operation, in milliseconds. Before the specified amount of time has elapsed, the service should make its next call to the SetServiceStatus function with either an incremented dwCheckPoint value or a change in dwCurrentState. If the amount of time specified by dwWaitHint passes, and dwCheckPoint has not been incremented ordwCurrentState has not changed, the service control manager or service control program can assume that an error has occurred and the service should be stopped. However, if the service shares a process with other services, the service control manager cannot terminate the service application because it would have to terminate the other services sharing the process as well.

A sample code showing the use of above discussed variable :-

// Purpose: 
//   It will set the current service status and reports it to the SCM.
// Parameters:
//   dwCurrentState - The current state (see SERVICE_STATUS)
//   dwWin32ExitCode - The system error code
//   dwWaitHint - Estimated time for pending operation, 
//     in milliseconds
VOID ReportServiceStatus( DWORD dwCurrentState,
                      DWORD dwWin32ExitCode,
                      DWORD dwWaitHint)
    static DWORD dwCheckPoint = 1;
    // Fill in the SERVICE_STATUS structure.
    gSvcStatus.dwCurrentState = dwCurrentState;
    gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
    gSvcStatus.dwWaitHint = dwWaitHint;
    if (dwCurrentState == SERVICE_START_PENDING)
        gSvcStatus.dwControlsAccepted = 0;
    else gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
    if ( (dwCurrentState == SERVICE_RUNNING) ||
           (dwCurrentState == SERVICE_STOPPED) )
        gSvcStatus.dwCheckPoint = 0;
    else gSvcStatus.dwCheckPoint = dwCheckPoint++;
    // Report the status of the service to the SCM.
    SetServiceStatus( gSvcStatusHandle, &gSvcStatus );

One more important thing to remember :

Ø   Do not wait longer than the wait hint. A good interval is one-tenth of the wait hint but not less than 1 second  and not more than 10 seconds.  
        dwWaitTime = ssStatus.dwWaitHint / 10;
        if( dwWaitTime < 1000 )
            dwWaitTime = 1000;
        else if ( dwWaitTime > 10000 )
            dwWaitTime = 10000;

The following are some of the best practices when calling SetServiceStatus function:
  • Initialize all fields in the SERVICE_STATUS structure, ensuring that there are valid check-point and wait hint values for pending states. Use reasonable wait hints.
  • Do not register to accept controls while the status is SERVICE_START_PENDING or the service can crash. After initialization is completed, accept the SERVICE_CONTROL_STOP code.
  • Call this function with checkpoint and wait-hint values only if the service is making progress on the tasks related to the pending start, stop, pause, or continue operation. Otherwise, SCM cannot detect if your service is hung.
  • Enter the stopped state with an appropriate exit code if ServiceMain fails.
  • If the status is SERVICE_STOPPED, perform all necessary cleanup and call SetServiceStatus one time only. This function makes an LRPC call to the SCM. The first call to the function in the SERVICE_STOPPED state closes the RPC context handle and any subsequent calls can cause the process to crash.
  • Do not attempt to perform any additional work after calling SetServiceStatus with SERVICE_STOPPED, because the service process can be terminated at any time.


It will also help in addressing the problem "service did not respond in a timely fashion"

Post a Comment