HOWTO: Manage Threads in a Windows NT System Service
ID: Q189996
|
The information in this article applies to:
-
Microsoft Win32 Application Programming Interface (API), used with:
-
Microsoft Windows NT 4.0
-
Microsoft Windows 2000
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 kbWinOS2000 kbService kbThread kbDSupport kbGrpKernBase
Version : winnt:4.0
Platform : winnt
Issue type : kbhowto