HOWTO: Manage Threads in a Windows NT System Service

ID: Q189996

The information in this article applies to:

SUMMARY

Due to the strongly enforced communication between a service and the Service Control Manager (SCM), you need to follow special steps when you use multiple threads in a service. When a service is instructed to stop by the SCM, all of the service's child threads must be exited before reporting to the SCM that the service is stopped. Otherwise, the SCM can become confused about the state of the service and it might fail to shutdown correctly.

MORE INFORMATION

The Service Control Manager (SCM) controls a service by sending service control events to the service's control handler routine. The service must respond to control events in a timely manner so that the SCM can keep track of the state of the service. Also, the state of the service must match the description of its state that the SCM receives.

Multi-threaded services must be handled very carefully to prevent the SCM from becoming confused about the service's state. When the service is instructed by the SCM to stop the service's main thread, it must wait for all the child threads to exit before reporting to the SCM that the service is stopped through the SetServiceStatus() API. To keep the SCM informed of the service's state, there must be a wait for the child threads. The SCM needs to be notified that the service is responding to the stop control event and that progress is being made in stopping the service. The SCM will assume the service is making progress if the service responds (through SetServiceStatus() ) within the time (wait hint) specified in the previous SetServiceStatus() call and the "check point" is updated to be greater than the checkpoint specified in the previous call to SetServiceStatus().

If the service reports to the SCM that the service has stopped before all the child threads have exited, it is possible that the SCM will interpret this as a contradiction. This might result in a state where the service cannot be stopped or restarted.

The following sample code demonstrates how a simple service can spawn worker threads, respond to the SCM, notify the threads to exit, keep the SCM notified of the state and progress of the service, and report to the SCM that the service is stopped. To install the service, build it as a Win32 Console Application and use the SC utility included with either the Windows NT Resource Kit or the Win32 Platform SDK. Use the Service Control applet in the Control Panel to start and stop the service.

Sample Code

   /*++

      Copyright (c) 1998  Microsoft Corporation

      Module Name:

         threadbased.c

      Description:
          This sample illustrates how to manage threads in a Windows NT
          System Service. To install or remove the service, build the
          executable as a Win32 Console Application and use the SC utility
          in the Windows NT Resource Kit. See the Simple Service Sample
          in the Win32 SDK for sample code to install and remove a service.

          The following import libraries are required:
             advapi32.lib
             user32.lib

      Dave McPherson (davemm)   11-March-98

   --*/ 

   #include <windows.h>
   #include <tchar.h>
   #include <stdio.h>

   // 
   // Global variables.
   // 
   HANDLE  hStopEvent;
   HANDLE hThreads[3] = {NULL,NULL,NULL};
   LPTSTR  lpszServiceName;
   SERVICE_STATUS_HANDLE   ssh;

   // 
   // Function prototypes.
   // 
   DWORD WINAPI ThreadProc(LPVOID lpParameter);
   void  WINAPI  Service_Main(DWORD dwArgc, LPTSTR *lpszArgv);
   void  WINAPI  Service_Ctrl(DWORD dwCtrlCode);
   void  ErrorStopService(LPTSTR lpszAPI);
   void  SetTheServiceStatus(DWORD dwCurrentState,DWORD dwWin32ExitCode,
                             DWORD dwCheckPoint,  DWORD dwWaitHint);

   // 
   // _tmain -  Entry point for service. Calls StartServiceCtrlDispatcher
   //           and then blocks until the ServiceMain function returns.
   // 
   void _tmain(int argc, TCHAR *argv[])
   {
      SERVICE_TABLE_ENTRY ste[] =
           {{TEXT(""),(LPSERVICE_MAIN_FUNCTION)Service_Main},{NULL, NULL}};

      OutputDebugString(TEXT("Entered service code\n"));

      if (!StartServiceCtrlDispatcher(ste))
      {
         TCHAR error[256];

         wsprintf(error,
              TEXT("Error code for StartServiceCtrlDispatcher: %u.\n"),
              GetLastError());
         OutputDebugString(error);
      }
      else
         OutputDebugString(TEXT("StartServiceCtrlDispatcher returned!\n"));
   }

   // 
   // Service_Main -   This is called by the service control manager after
   //                  the call to StartServiceCtrlDispatcher.
   // 
   void WINAPI Service_Main(DWORD dwArgc, LPTSTR *lpszArgv)
   {
      DWORD ThreadId;
      DWORD t;
      DWORD dwWaitRes;

      // Obtain the name of the service.
      lpszServiceName = lpszArgv[0];

      // Register the service ctrl handler.
      ssh = RegisterServiceCtrlHandler(lpszServiceName,
                                         (LPHANDLER_FUNCTION)Service_Ctrl);

      // Create the event to signal the service to stop.
      hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
      if (hStopEvent == NULL)
         ErrorStopService(TEXT("CreateEvent"));

      /*******************************************************************/ 
      // This is where you can put one-time work that you want to complete
      // before starting.
      for (t=0;t<3;t++)
      {
         hThreads[t] = CreateThread(NULL,0,ThreadProc,
                                   (LPVOID)t,0,&ThreadId);
         if (hThreads[t] == INVALID_HANDLE_VALUE)
            ErrorStopService(TEXT("CreateThread"));
      }

      /*******************************************************************/ 
      // The service has started.
      SetTheServiceStatus(SERVICE_RUNNING, 0, 0, 0);
      OutputDebugString(TEXT("SetTheServiceStatus, SERVICE_RUNNING\n"));

      // 
      // Main loop for the service.   <-----------------------------
      // 
      while(WaitForSingleObject(hStopEvent, 1000) != WAIT_OBJECT_0){

         /***************************************************************/ 
         // Main loop for service.
         /***************************************************************/ 
      }

      // Now wait for threads to exit.
      for (t=1;TRUE;t++)
      {
         if ((dwWaitRes = WaitForMultipleObjects(3,hThreads,TRUE,1000))
                                                          == WAIT_OBJECT_0)
            break;
         else if((dwWaitRes == WAIT_FAILED)||(dwWaitRes==WAIT_ABANDONED))
         ErrorStopService(TEXT("WaitForMultipleObjects"));
         else
            SetTheServiceStatus(SERVICE_STOP_PENDING, 0, t, 3000);
      }

      // close the event handle and the thread handle
      if (!CloseHandle(hStopEvent))
         ErrorStopService(TEXT("CloseHandle"));
      if (!CloseHandle(hThreads[0]))
         ErrorStopService(TEXT("CloseHandle"));
      if (!CloseHandle(hThreads[1]))
         ErrorStopService(TEXT("CloseHandle"));
      if (!CloseHandle(hThreads[2]))
         ErrorStopService(TEXT("CloseHandle"));

      // Stop the service.
      OutputDebugString(TEXT("SetTheServiceStatus, SERVICE_STOPPED\n"));
      SetTheServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0);
   }

   // 
   // Service_Ctrl -   Where control signals from the Service Control Mgr
   //                  are handled.
   // 
   void WINAPI Service_Ctrl(DWORD dwCtrlCode)
   {
      DWORD dwState = SERVICE_RUNNING;

      switch(dwCtrlCode)
      {
         case SERVICE_CONTROL_STOP:
            dwState = SERVICE_STOP_PENDING;
            break;

         case SERVICE_CONTROL_SHUTDOWN:
            dwState = SERVICE_STOP_PENDING;
            break;

         case SERVICE_CONTROL_INTERROGATE:
            break;

         default:
            break;
      }

      // Set the status of the service.
      SetTheServiceStatus(dwState, NO_ERROR, 0, 0);
      OutputDebugString(
                 TEXT("SetTheServiceStatus, Service_Ctrl function\n"));

      // Tell service_main thread to stop.
      if ((dwCtrlCode == SERVICE_CONTROL_STOP) ||
                                 (dwCtrlCode == SERVICE_CONTROL_SHUTDOWN))
      {
         if (!SetEvent(hStopEvent))
            ErrorStopService(TEXT("SetEvent"));
         else
            OutputDebugString(TEXT("Signal service_main thread\n"));
      }
   }

   // 
   // ThreadProc -   Thread procedure for all three worker threads.
   // 
   DWORD WINAPI ThreadProc(LPVOID lpParameter)
   {
      INT nThreadNum = (INT)lpParameter;
      TCHAR szOutput[25];

      while(WaitForSingleObject(hStopEvent, 1000) != WAIT_OBJECT_0)
      {
      // Just to have something to do, it will beep every second.
         Sleep(1000);
         wsprintf(szOutput,TEXT("\nThread %d says Beep\n"),nThreadNum);
         OutputDebugString(szOutput); //Send visual to debugger.
      }

      return 0;
   }

   // 
   //  SetTheServiceStatus -   This just wraps up SetServiceStatus.
   // 
   void SetTheServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode,
                            DWORD dwCheckPoint,   DWORD dwWaitHint)
   {
      SERVICE_STATUS ss;  // Current status of the service.

      // 
      // Disable control requests until the service is started.
      // 
      if (dwCurrentState == SERVICE_START_PENDING)
         ss.dwControlsAccepted = 0;
      else
         ss.dwControlsAccepted =
                       SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN;
                       // Other flags include SERVICE_ACCEPT_PAUSE_CONTINUE
                       // and SERVICE_ACCEPT_SHUTDOWN.

      // Initialize ss structure.
      ss.dwServiceType             = SERVICE_WIN32_OWN_PROCESS;
      ss.dwServiceSpecificExitCode = 0;
      ss.dwCurrentState            = dwCurrentState;
      ss.dwWin32ExitCode           = dwWin32ExitCode;
      ss.dwCheckPoint              = dwCheckPoint;
      ss.dwWaitHint                = dwWaitHint;

      // Send status of the service to the Service Controller.
      if (!SetServiceStatus(ssh, &ss))
         ErrorStopService(TEXT("SetServiceStatus"));
   }

   // 
   //  ErrorStopService -  Use this when there is an API error or bad
   //                      situation this just ends the service and
   //                      displays an error message to the debugger.
   // 
   void ErrorStopService(LPTSTR lpszAPI)
   {
      INT t;
      TCHAR   buffer[256]  = TEXT("");
      TCHAR   error[1024]  = TEXT("");
      LPVOID lpvMessageBuffer;
      DWORD  dwWaitRes;

      wsprintf(buffer,TEXT("API = %s, "), lpszAPI);
      lstrcat(error, buffer);

      ZeroMemory(buffer, sizeof(buffer));
      wsprintf(buffer,TEXT("error code = %d, "), GetLastError());
      lstrcat(error, buffer);

      // Obtain the error string.
      FormatMessage(
                FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
                NULL, GetLastError(),
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPTSTR)&lpvMessageBuffer, 0, NULL);

      ZeroMemory((LPVOID)buffer, (DWORD)sizeof(buffer));
      wsprintf(buffer,TEXT("message = %s"), (TCHAR *)lpvMessageBuffer);
      lstrcat(error, buffer);

      // Free the buffer allocated by the system.
      LocalFree(lpvMessageBuffer);

      // Write the error string to the debugger.
      OutputDebugString(error);

      // If you have threads running, tell them to stop. Something went
      // wrong, and you need to stop them so you can inform the SCM.
      SetEvent(hStopEvent);

      // Wait for the threads to stop.
      for (t=1;TRUE;t++)
      {
         if ((dwWaitRes = WaitForMultipleObjects(3,hThreads,TRUE,1000))
                                                          == WAIT_OBJECT_0)
            break;
         else if ((dwWaitRes== WAIT_FAILED)||(dwWaitRes== WAIT_ABANDONED))
            break; // Our wait failed
         else
         {
            SetTheServiceStatus(SERVICE_STOP_PENDING, 0, t, 3000);
         }
      }

      // Stop the service.
      SetTheServiceStatus(SERVICE_STOPPED, GetLastError(), 0, 0);

   }

Additional query words: Service Shutdown Hang
Keywords          : kbKernBase kbNTOS400 kbService kbThread kbGrpKernBase 
Issue type        : kbhowto

Last Reviewed: July 26, 1998