PRB: ServiceMain thread May Get Terminated in DllMain() when a Service Process Exits Normally
ID: Q201349
|
The information in this article applies to:
-
Microsoft Win32 Application Programming Interface (API), included with:
-
Microsoft Windows NT 4.0
-
Microsoft Windows 2000
SYMPTOMS
If a service implicitly loads a DLL, the thread created by the Service Control Manager (SCM) to execute ServiceMain() can be terminated while it is still executing DLL_THREAD_DETACH code in the library's DllMain() function. This premature termination can cause unpredictable results if the thread was supposed to perform critical cleanup code in DllMain().
CAUSE
When the primary thread of a service calls StartServiceCtrlDispatcher(), the SCM creates a thread to do the actual work in ServiceMain(). Therefore, all services have at least two threads: a primary thread and a ServiceMain() thread.
When all of the services in a process have stopped; that is, each one has set its status to SERVICE_STOPPED, the primary thread returns from StartServiceCtrlDispatcher(). If this thread exits immediately, the ServiceMain() thread may still be performing cleanup work in the DLL_THREAD_DETACH code of an implicitly loaded DLL. If the primary thread calls ExitProcess() on its way out (which is the behavior of applications using the Microsoft C-Runtime Library), the ServiceMain() thread will be terminated without warning.
RESOLUTION
To work around this problem, the primary thread can wait on the ServiceMain() thread to complete its cleanup work by waiting on a handle to the ServiceMain() thread after returning from StartServiceCtrlDispatcher(). This approach is demonstrated in the code sample at the end of this article.
STATUS
This behavior is by design.
MORE INFORMATION
By default, the primary thread does not have a handle to the ServiceMain() thread, since the ServiceMain() thread is created indirectly by the SCM. To make this handle available to the primary thread, the ServiceMain() thread duplicates its own handle when it begins executing. The duplicate handle is stored in a global variable. After the primary thread returns from StartServiceCtrlDispatcher(), it waits on the global thread handle using WaitForSingleObject(). This allows the ServiceMain() thread to complete any cleanup work it needs to perform before the primary thread exits. The primary thread then closes the duplicated ServiceMain() thread handle and continues executing its natural conclusion.
The following bare-bones service demonstrates this approach:
//**********************************************************************
//
// Bare Bones Service
//
// This program is a mere skeleton for a Windows NT/Windows 2000
// service. In its current state, it provides virtually no useful
// functionality whatsoever. Do with it as you will, but keep in
// mind the following...
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 1999 Microsoft Corporation. All rights reserved.
// Author: Jonathan Russ (jruss)
//
//**********************************************************************
#define _WIN32_WINNT 0x0400
#define SERVICE_NAME TEXT("BareBonesService")
#include <windows.h>
// declare global handle for the ServiceMain() thread
HANDLE g_hServiceMainThread = NULL;
// declare global handle for the completion port that
// will be used to pass service control requests to the
// ServiceMain() thread
HANDLE g_hCompPort = NULL;
void WINAPI ServiceMain( DWORD dwArgc, LPTSTR *lpszArgv );
void WINAPI ServiceHandler( DWORD dwControlCode );
void main( void ) {
SERVICE_TABLE_ENTRY ste[] = {
{ SERVICE_NAME, ServiceMain },
{ NULL, NULL }
};
StartServiceCtrlDispatcher( ste );
// wait for the ServiceMain() thread to exit
if ( g_hServiceMainThread )
WaitForSingleObject( g_hServiceMainThread, INFINITE );
// release global thread handle
CloseHandle( g_hServiceMainThread );
return;
}
void WINAPI ServiceMain( DWORD dwArgc, LPTSTR *lpszArgv ) {
SERVICE_STATUS ss;
SERVICE_STATUS_HANDLE hService;
DWORD dwBytesTransferred;
DWORD dwControlCode;
OVERLAPPED * po;
// set static members of service status structure
ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS; // one service
ss.dwControlsAccepted = SERVICE_ACCEPT_PAUSE_CONTINUE
| SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
// set error members of service status structure
ss.dwWin32ExitCode = NO_ERROR;
ss.dwServiceSpecificExitCode = NO_ERROR;
// create a duplicate of this thread's handle so the
// primary thread can wait on it before exiting
if ( !DuplicateHandle( GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &g_hServiceMainThread, 0, 0,
DUPLICATE_SAME_ACCESS ) ) {
g_hServiceMainThread = NULL;
goto cleanup;
}
// create the completion port
g_hCompPort = CreateIoCompletionPort( INVALID_HANDLE_VALUE,
NULL, 0, 0 );
if ( !g_hCompPort )
goto cleanup;
// register the ServiceHandler() function to receive control requests
hService = RegisterServiceCtrlHandler( SERVICE_NAME,
ServiceHandler );
// set service status to start pending
ss.dwCurrentState = SERVICE_START_PENDING;
ss.dwCheckPoint = 1;
ss.dwWaitHint = 500;
SetServiceStatus( hService, &ss );
// TODO: add additional initialization code here...
// set service status to running
ss.dwCurrentState = SERVICE_RUNNING;
ss.dwCheckPoint = ss.dwWaitHint = 0;
SetServiceStatus( hService, &ss );
do {
GetQueuedCompletionStatus( g_hCompPort, &dwBytesTransferred,
&dwControlCode, &po, INFINITE );
switch (dwControlCode) {
case SERVICE_CONTROL_PAUSE:
if ( ss.dwCurrentState = SERVICE_RUNNING ) {
// set service status to pause pending
ss.dwCurrentState = SERVICE_PAUSE_PENDING;
ss.dwCheckPoint = 1;
ss.dwWaitHint = 500;
SetServiceStatus( hService, &ss );
// TODO: pause all execution here...
// set service status to paused
ss.dwCurrentState = SERVICE_PAUSED;
ss.dwCheckPoint = ss.dwWaitHint = 0;
SetServiceStatus( hService, &ss );
}
break;
case SERVICE_CONTROL_CONTINUE:
if ( ss.dwCurrentState = SERVICE_PAUSED ) {
// set service status to continue pending
ss.dwCurrentState = SERVICE_CONTINUE_PENDING;
ss.dwCheckPoint = 1;
ss.dwWaitHint = 500;
SetServiceStatus( hService, &ss );
// TODO: continue execution here...
// set service status to running
ss.dwCurrentState = SERVICE_RUNNING;
ss.dwCheckPoint = ss.dwWaitHint = 0;
SetServiceStatus( hService, &ss );
}
break;
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
if ( ss.dwCurrentState = SERVICE_RUNNING ) {
// set service status to stop pending
ss.dwCurrentState = SERVICE_STOP_PENDING;
ss.dwCheckPoint = 1;
ss.dwWaitHint = 500;
SetServiceStatus( hService, &ss );
}
break;
case SERVICE_CONTROL_INTERROGATE:
ss.dwCheckPoint = ss.dwWaitHint = 0;
SetServiceStatus( hService, &ss );
break;
}
} while ( ss.dwCurrentState != SERVICE_STOP_PENDING );
cleanup:
// TODO: perform cleanup work here...
// set service status to stopped
ss.dwCurrentState = SERVICE_STOPPED;
ss.dwCheckPoint = ss.dwWaitHint = 0;
SetServiceStatus( hService, &ss );
// safely perform final cleanup work while confidently
// knowing the primary thread will not terminate this thread...
CloseHandle( g_hCompPort );
}
void WINAPI ServiceHandler( DWORD dwControlCode ) {
// post status to a completion port to avoid tying up
// the primary thread
PostQueuedCompletionStatus( g_hCompPort, 0, dwControlCode, NULL );
}
Additional query words:
kbDSupport
Keywords : kbAPI kbDLL kbKernBase kbSCM kbService kbDSupport kbGrpKernBase
Version : winnt:4.0
Platform : winnt
Issue type : kbprb