Microsoft KB Archive/201349

{|
 * width="100%"|

PRB: ServiceMain thread May Get Terminated in DllMain when a Service Process Exits Normally

 * }

Q201349

-

The information in this article applies to:


 * Microsoft Win32 Application Programming Interface (API), included with:
 * Microsoft Windows NT Server version 4.0
 * Microsoft Windows NT Workstation version 4.0
 * the operating system: 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 

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

Issue type : kbprb

Technology : kbAudDeveloper kbWin32sSearch kbWin32API