You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
567 lines
16 KiB
567 lines
16 KiB
/*++
|
|
|
|
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<JobFunction>
|
|
(CVssWorkerThread::_ThreadFunction), // Start address
|
|
reinterpret_cast<void*>(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<CVssWorkerThread*>( 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__
|