/*++ Copyright (c) 1999 Microsoft Corporation Module Name: Worker.hxx Abstract: Declaration of CVssWorkerThread Adi Oltean [aoltean] 10/10/1999 Revision History: Name Date Comments aoltean 10/10/1999 Created aoltean 11/02/1999 Adding asserts and traces. Removing TerminateThread. --*/ #ifndef __VSS_WORKER_HXX__ #define __VSS_WORKER_HXX__ #if _MSC_VER > 1000 #pragma once #endif ///////////////////////////////////////////////////////////////////////////// // Includes #include "vssmsg.h" //////////////////////////////////////////////////////////////////////// // Standard foo for file name aliasing. This code block must be after // all includes of VSS header files. // #ifdef VSS_FILE_ALIAS #undef VSS_FILE_ALIAS #endif #define VSS_FILE_ALIAS "INCWORKH" // //////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // CVssWorkerThread /* 1) This abstract class is used to implement a generic worker thread. The derived class CMyJob must be in the form: class CMyJob: CVssWorkerThread { // Destructor public: ~CMyJob() { FinalReleaseWorkerThreadObject(); }; // Ovverides protected: // called after thread creation but before thread starting. // Called in the creator's thread. bool OnInit(); { // Initialize some internal variables return true; }; // called in the background thread void OnRun() { // Check if the user wants to terminate the thread while (!MustBeTerminated()) { // Do some lengthly task ... } }; // called after finishing the thread procedure. // in the same background thread as the OnRun void OnFinish() { // Normal uninitialization ... // Signal the job object as finished (mandatory call here) MarkAsFinished(); // You can do auto-destroy here .. } void CVssAsync::OnTerminate() { // Called on forced termination } }; 2) Generic usage (an example) HRESULT hr = S_OK; CMyJob* pJob = new CMyJob; if (pJob == NULL) hr = E_OUTOFMEMORY; if (SUCEEDED(hr)) hr = pJob->PrepareJob(); // Create the background thread in SUSPENDED state if (SUCEEDED(hr)) hr = pJob->StartJob(); // Run the background thread in SUSPENDED state if (SUCCEEDED(hr)) if (!::WaitForSingleObject(pJob->GetThreadID())) hr = HRESULT_FROM_WIN32(GetLastError()); delete pJob; WARNINGS: 1) DO NOT ALLOCATE A CVssWorkerThread OBJECT ON THE STACK! 2) The caller is responsible to call FinalReleaseWorkerThreadObject before destroying the object! 3) DO NOT destroy the job object before job termination! (i.e. before MarkOnFinish was called in OnFinish) */ class CVssWorkerThread { typedef unsigned ( __stdcall *JobFunction )( void * ); public: // The thread possible states typedef enum _VSS_ENUM_THREAD { VSS_THREAD_UNKNOWN = 0, // Invalid state VSS_THREAD_INIT, // Worker object constructed but PrepareJob not called yet. VSS_THREAD_PREPARED, // PrepareJob called but StartJob not called yet VSS_THREAD_RUNNING, // StartJob called but hte background thread not yet finished. VSS_THREAD_FINISHED, // Background thread finished. VSS_THREAD_ERROR // An error was encountered in one of the phases above. } VSS_ENUM_THREAD; // Constructors& destructors private: // disable copy constructor CVssWorkerThread(const CVssWorkerThread&); public: // default constructor CVssWorkerThread(): m_hThread(NULL), m_eThreadState(VSS_THREAD_INIT), m_nThreadID(0), m_bInitSucceeded(false), m_bTerminateNow(false), m_bStarted(false) {}; protected: // destructor ~CVssWorkerThread() { if (m_hThread) { BS_ASSERT( (m_eThreadState == VSS_THREAD_INIT) || (m_eThreadState == VSS_THREAD_FINISHED) || (m_eThreadState == VSS_THREAD_ERROR)); // close the handle to the thread since no-one is keeping it. ::CloseHandle(m_hThread); m_hThread = NULL; } } // Opperations - to be called only from the derived class protected: void MarkAsFinished() /*++ This method marks the successful thread termination Called only by OnFinish method to notify that the thread succesfully terminates This must not be called when the OnFinish reason is "TERMINATE" It is illegal to mark a error-state thread as finished. --*/ { if (m_eThreadState == VSS_THREAD_RUNNING) m_eThreadState = VSS_THREAD_FINISHED; else BS_ASSERT(false); } void FinalReleaseWorkerThreadObject() /*++ This method kills the NT background thread, if still running Must be called as a last method for killing the thread, For example it is called in the destructor of the final derived class. It might be called from any thread exceptig the running BACKGROUND thread. WARNING: This function calls virtual function. DO NOT call this function in the destructor of an instance which is not the final derived class, otherwise only the virtual functions of that class or the base classes will be called. --*/ { CVssFunctionTracer ft( VSSDBG_COORD, L"CVssWorkerThread::FinalReleaseWorkerThreadObject" ); try { // Wait until the background thread is terminated. switch(m_eThreadState) { case VSS_THREAD_PREPARED: Terminate(true); break; case VSS_THREAD_RUNNING: Terminate(); break; case VSS_THREAD_INIT: case VSS_THREAD_FINISHED: case VSS_THREAD_ERROR: break; default: ft.Trace( VSSDBG_GEN, L"Bad state %d", m_eThreadState); BS_ASSERT(false); break; } } VSS_STANDARD_CATCH(ft) } // Main operations - called from the clients of the derived class. public: HRESULT PrepareJob() /*++ Description: This method prepares the NT background thread for running but it doesn't start it. Called by: - Must be called in order to prepare the background thread. This will call internally the OnInit method in the CLIENT thread. - The next method to call is StartJob, to fire up the created thread. Calling thread: - The CLIENT thread What to do on failures: - Do not call any other methods. Just delete the object instance. - Beware that the internal state is VSS_THREAD_ERROR now. Return values: E_OUTOFMEMORY - _beginthreadex failed E_UNEXPECTED - Function called in improper state: programming error --*/ { CVssFunctionTracer ft( VSSDBG_COORD, L"CVssWorkerThread::PrepareJob" ); // Test for valid state if ((m_hThread != NULL) || (m_eThreadState != VSS_THREAD_INIT)) { ft.Trace( VSSDBG_GEN, L"Bad state %d", m_eThreadState); BS_ASSERT(false); // programming error return E_UNEXPECTED; } // Start the expiration thread uintptr_t nResult = ::_beginthreadex( NULL, // Security descriptor 0, // Stack size reinterpret_cast (CVssWorkerThread::_ThreadFunction), // Start address reinterpret_cast(this), // Arg list for the new thread. CREATE_SUSPENDED, // Initflag &m_nThreadID // Will receive the thread ID ); // BUG in _beginthreadex - sometimes it returns zero under memory pressure. // Treat the case of a NULL returned handle if (nResult == 0) { ft.Trace( VSSDBG_GEN, L"_beginthreadex failed. errno = 0x%08lx", errno); ft.LogError( VSS_ERROR_THREAD_CREATION, VSSDBG_GEN << (HRESULT) nResult << (HRESULT) (errno) ); return E_OUTOFMEMORY; // Probably invalid arguments in _beginthreadex... } // Error treatment if (nResult == (uintptr_t)(-1)) { // Interpret the error code. if (errno == EAGAIN) return E_OUTOFMEMORY; // Try again later... else { ft.Trace( VSSDBG_GEN, L"_beginthreadex failed. errno = 0x%08lx", errno); ft.LogError( VSS_ERROR_THREAD_CREATION, VSSDBG_GEN << (HRESULT) nResult << (HRESULT) (errno) ); return E_OUTOFMEMORY; // Probably invalid arguments in _beginthreadex... } } // Get the thread handle m_hThread = (HANDLE) nResult; // Call OnInit on the base class. m_bInitSucceeded = OnInit(); // Will be used in ThreadFunction in the resumed thread. m_eThreadState = VSS_THREAD_PREPARED; return S_OK; } HRESULT StartJob() /*++ Description: This method starts the prepared NT background thread . Called by: - This will call internally the OnInit method in the BACKGROUND thread. Calling thread: - The CLIENT thread What to do on failures: - Do not call any other methods. Just delete the object instance. - Beware that the internal state is VSS_THREAD_ERROR now. Return values: E_OUTOFMEMORY - ResumeThread failed E_UNEXPECTED - Function called in improper state: programming error --*/ { CVssFunctionTracer ft( VSSDBG_COORD, L"CVssWorkerThread::StartJob" ); // Test for valid state if ((m_hThread == NULL) || (m_eThreadState != VSS_THREAD_PREPARED)) { ft.Trace( VSSDBG_GEN, L"Bad state %d", m_eThreadState); BS_ASSERT(false); // programming error return E_UNEXPECTED; } // Resume the thread. Here ThreadFunction will be called on the resumed thread. DWORD dwResult = ::ResumeThread( m_hThread ); // Check if the thread was in suspended state. BS_ASSERT(dwResult != 0); // Check for error if (dwResult == 0xFFFFFFFF) { ft.LogGenericWarning( VSSDBG_GEN, L"ResumeThread(%p) = -1, GetLastError() == 0x%08lx, m_eThreadState == %d", m_hThread, GetLastError(), (INT)m_eThreadState ); ft.Trace( VSSDBG_GEN, L"ResumeThread failed. Error: 0x%08lx. State: %d", GetLastError(), m_eThreadState); // Reset the object state m_eThreadState = VSS_THREAD_ERROR; return E_OUTOFMEMORY; // Error resuming the thread } m_bStarted = true; return S_OK; } // Attributes public: // Get the current thread state VSS_ENUM_THREAD GetThreadState() const { return m_eThreadState; }; // Get the current thread's handle HANDLE GetThreadHandle() const { return m_hThread; }; // Get the current thread's ID UINT GetThreadID() const { return m_nThreadID; }; // Check if the thread was marked for Termination // Called from the OnRun implementation bool MustBeTerminated() const { return m_bTerminateNow; }; // Shortcut for checking if the worker thread is resumed. // Must be called from the same thread as StartJob. bool IsStarted() const { return m_bStarted; }; // Ovverides - these must be defined in the derived class protected: // Entry point called after thread creation but before thread starting. // The caller thread is the CLIENT thread virtual bool OnInit() = 0; // Entry point for the thread procedure. // The caller thread is the BACKGROUND thread virtual void OnRun() = 0; // Entry point called after finishing hte thread procedure. // The caller thread is the BACKGROUND thread // WARNING: this function may RELEASE the worker thread object!!! virtual void OnFinish() = 0; // Entry point called when the thread will be terminated // The caller thread is the CLIENT thread // The derived class implementation can use this to signal to the // running thread a termination event virtual void OnTerminate() = 0; // Internal operations private: // The thread function static unsigned __stdcall _ThreadFunction(LPVOID* ptrArg) { CVssWorkerThread* pObj = reinterpret_cast( ptrArg ); pObj->ThreadFunction(); return 0; } // The C++ version of the thread function void ThreadFunction() { CVssFunctionTracer ft( VSSDBG_COORD, L"CVssWorkerThread::ThreadFunction" ); m_eThreadState = VSS_THREAD_RUNNING; // Run the job, if OnInit succeeded if (m_bInitSucceeded && !m_bTerminateNow) OnRun(); // Finish the job, regardless of the fact that OnRun was caleld or not. OnFinish(); } HRESULT Terminate( IN bool bThreadPreparedOnly = false ) /*++ Description: This method terminates prepared NT background thread. It does NOT call the TerminateThread win32 API. Called by: FinalReleaseWorkerThreadObject() Calling thread: The CLIENT thread What to do on failures: - Do not call any other methods. Just delete the object instance. - Beware that the internal state is VSS_THREAD_ERROR now. --*/ // Does not call OnFinish... unless something wrong happens. { CVssFunctionTracer ft( VSSDBG_COORD, L"CVssWorkerThread::Terminate" ); if (m_hThread) { // Try to terminate the thread. The OnRun code must periodically check the // m_bTerminateNow variable by callign MustBeTerminated method. m_bTerminateNow = true; // Notify that the thread is terminating... OnTerminate(); if (bThreadPreparedOnly) // TRUE only if the thread is supposed to be non-resumed. { // Resume the thread. Here ThreadFunction will be called on the resumed thread. DWORD dwResult = ::ResumeThread( m_hThread ); // Check for error if (dwResult == 0xFFFFFFFF) { ft.LogGenericWarning( VSSDBG_GEN, L"ResumeThread(%p) = -1, GetLastError() == 0x%08lx, m_eThreadState == %d", m_hThread, GetLastError(), (INT)m_eThreadState ); ft.Trace( VSSDBG_GEN, L"ResumeThread failed. Error: 0x%08lx. State: %d", GetLastError(), m_eThreadState); // Reset the object state m_eThreadState = VSS_THREAD_ERROR; return E_UNEXPECTED; // Error resuming the thread } } if (::WaitForSingleObject( m_hThread, INFINITE ) == WAIT_FAILED) { ft.LogGenericWarning( VSSDBG_GEN, L"WaitForSingleObject(%p,INFINITE) == WAIT_FAILED, GetLastError() == 0x%08lx, m_eThreadState == %d", m_hThread, GetLastError(), (INT)m_eThreadState ); ft.Trace( VSSDBG_GEN, L"WaitForSingleObject failed. Error: 0x%08lx. State: %d", GetLastError(), m_eThreadState); // Reset the object state m_eThreadState = VSS_THREAD_ERROR; return E_UNEXPECTED; // Error resuming the thread } } return S_OK; } // Internal data members. private: bool m_bInitSucceeded; bool m_bTerminateNow; HANDLE m_hThread; UINT m_nThreadID; VSS_ENUM_THREAD m_eThreadState; bool m_bStarted; // To check that the worker thread is resumed. }; #endif // __VSS_WORKER_HXX__