//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================


#include "stdafx.h"
#include "tslist.h"
#include <workthreadpool.h>
#include <gclogger.h>

#include "tier0/memdbgon.h"

namespace GCSDK {

IWorkThreadPoolSignal *CWorkThreadPool::sm_pWorkItemsCompletedSignal = NULL;

//-----------------------------------------------------------------------------
// Purpose:	CWorkThread constructors
//-----------------------------------------------------------------------------
CWorkThread::CWorkThread( CWorkThreadPool *pThreadPool )
:	m_pThreadPool( pThreadPool ),
	m_bExitThread( false ),
	m_bFinished( false )
{
}

CWorkThread::CWorkThread( CWorkThreadPool *pThreadPool, const char *pszName )
:	m_pThreadPool( pThreadPool ),
	m_bExitThread( false ),
	m_bFinished( false )
{
	SetName( pszName );
}

//-----------------------------------------------------------------------------
// Purpose:	Tell work thread pool not to set event on every item added (SetEvent is very expensive)
//-----------------------------------------------------------------------------
void CWorkThreadPool::SetNeverSetEventOnAdd( bool bNeverSet ) 
{ 
	bool bWasSet = m_bNeverSetOnAdd;
	m_bNeverSetOnAdd = bNeverSet; 

	// In case of disabling set right away to make sure if we have pending work we execute it now with no latency
	if ( bWasSet && !m_bNeverSetOnAdd )
		m_EventNewWorkItem.Set();
}



//-----------------------------------------------------------------------------
// Purpose:	performs the work loop for the thread, waits for work,
//			notifies the owner (the pool) as it completes work and before it exits
//-----------------------------------------------------------------------------
int CWorkThread::Run()
{
	// manage our thread pool's statistics
	++m_pThreadPool->m_cThreadsRunning;

#ifdef _SERVER
	g_CompletionPortManager.AssociateCallingThreadWithIOCP();
#endif

	OnStart();

#if 0 // need to port over new vprof code
#if defined( VPROF_ENABLED )
	CVProfile *pProfile = GetVProfProfileForCurrentThread();
#endif
#endif

	CWorkThreadPool *pPool = m_pThreadPool;

	int nIterations = 0;
	const int nMaxFastIterations = 4;
	while ( !m_bExitThread )
	{
#if 0 // game vprof doesn't yet support TLS'd vprof instances, until new vprof code is ported
#if defined( VPROF_ENABLED )
		if ( pProfile )
			pProfile->MarkFrame( GetName() );
#endif
#endif
		pPool->m_cActiveThreads++;

		nIterations = 0;
		while ( (pPool->BNeverSetEventOnAdd() && nIterations < nMaxFastIterations) || nIterations == 0 )
		{
			// process any items which have arrived
			CWorkItem *pWorkItem = pPool->GetNextWorkItemToProcess( );
			while ( pWorkItem )
			{
#if 0
				pPool->m_StatWaitTime.Update( pWorkItem->WaitingTime() );
#endif
				if ( pWorkItem->HasTimedOut() )
				{
					pWorkItem->m_bCanceled = true;
				}
				else
				{
					// call the work item to do its work
					pWorkItem->m_bCanceled = false;

					CFastTimer fastTimer;
					fastTimer.Start();
					pWorkItem->m_bRunning = true;
					bool bSuccess = pWorkItem->ThreadProcess( this );
					pWorkItem->m_bRunning = false;
					fastTimer.End();
					CCycleCount cycleCount = fastTimer.GetDuration();
					pWorkItem->SetCycleCount(cycleCount);
#if 0
					pPool->m_StatExecutionTime.Update( cycleCount.GetUlMicroseconds() );
#endif
					if ( bSuccess )
						pPool->m_cSuccesses ++;
					else
						pWorkItem->m_bResubmit ? pPool->m_cRetries++ : pPool->m_cFailures++;
				}

				// do we need to resubmit this item?
				if ( pWorkItem->m_bResubmit )
				{
					pWorkItem->m_bResubmit = false;
					pWorkItem->m_bCanceled = false;
					// put it at the tail of the incoming queue 
					pPool->AddWorkItem( pWorkItem );
					pWorkItem->Release(); // dec since AddWorkItem added 1 more again
				}
				else
				{
					// put it in the outgoing queue 
					pPool->OnWorkItemCompleted( pWorkItem );
				}

				// If we are flagged as exiting don't try to get more work, we need to exit right away and orphan the work
				// to avoid blocking shutdown.
				if ( !m_bExitThread )
				{
					// get the next work item (if any)
					pWorkItem = pPool->GetNextWorkItemToProcess( );
				}
				else
				{
					pWorkItem = NULL;
				}

#if 0 // game vprof doesn't yet support TLS'd vprof instances, until new vprof code is ported
#if defined( VPROF_ENABLED )
				if ( pProfile && pWorkItem )
					pProfile->MarkFrame( GetName() );
#endif
#endif
			}

			if ( m_bExitThread )
				break;

			++nIterations;
			if ( pPool->BNeverSetEventOnAdd() && nIterations < nMaxFastIterations )
			{
				VPROF_BUDGET( "CWorkThread -- Sleep", VPROF_BUDGETGROUP_SLEEPING );
				ThreadSleep( 2 );
			}
		}

		pPool->m_cActiveThreads--;

		// wait for a new work item to arrive in the queue, check the counts first just to be sure
		{
			VPROF_BUDGET( "CWorkThread -- Sleep", VPROF_BUDGETGROUP_SLEEPING );
#ifdef _SERVER
			if ( pPool->BNeverSetEventOnAdd() )
				pPool->m_EventNewWorkItem.Wait( 15 );
			else
				pPool->m_EventNewWorkItem.Wait( 50 );
#else
			pPool->m_EventNewWorkItem.Wait( 50 );
#endif
		}
	}

	// Since we are exiting, we must have been signaled to shutdown, and we should signal any remaining threads
	// since each signal wakes only one thread.
	pPool->m_EventNewWorkItem.Set();
	
	m_bFinished = true;

	// updates stats
	--m_pThreadPool->m_cThreadsRunning;

	return EXIT_SUCCESS;
}


//-----------------------------------------------------------------------------
// Purpose:  Construct a new CWorkThreadPool object
//-----------------------------------------------------------------------------
CWorkThreadPool::CWorkThreadPool( const char *pszThreadName )
 :	
#if 0
    m_StatWaitTime( 100 ),
	m_StatExecutionTime( 100 ),
#endif
	m_bThreadsInitialized( false ),
	m_cThreadsRunning( 0 ),
	m_cActiveThreads( 0 ),
	m_bMayHaveJobTimeouts( false ),
	m_bExiting( false ),
	m_bAutoCreateThreads( false ),
	m_cMaxThreads( 0 ),
	m_cFailures( 0 ),
	m_cSuccesses( 0 ),
	m_pWorkThreadConstructor( NULL ),
	m_ulLastCompletedSequenceNumber( 0 ),
	m_ulLastUsedSequenceNumber( 0 ),
	m_ulLastDispatchedSequenceNumber( 0 ),
	m_bEnsureOutputOrdering( false ),
	m_bNeverSetOnAdd( false )
{
	Assert( pszThreadName != NULL );
	Q_strncpy( m_szThreadNamePfx, pszThreadName, sizeof( m_szThreadNamePfx ) );
	m_LimitTimerCreateNewThreads.SetLimit( 1 );

	m_pTSQueueToProcess = new CTSQueue< CWorkItem* >;
	m_pTSQueueCompleted = new CTSQueue< CWorkItem* >;
}


//-----------------------------------------------------------------------------
// Purpose:  destructor; does assertion checks to make sure we weere shut down cleanly
//			 cleans up even if we weren't cleanly stopped
//-----------------------------------------------------------------------------
CWorkThreadPool::~CWorkThreadPool() 
{
	// If you hit this you probably didn't call StopWorkThreads() first
	AssertMsg1( ( !m_bThreadsInitialized || m_bExiting ) && 0 == m_cThreadsRunning,
		"CWorkThreadPool::~CWorkThreadPool(): Thread pool %s shutdown incorrectly.\n",
		m_szThreadNamePfx );

	if ( m_WorkThreads.Count() )
	{
		StopWorkThreads();
		Assert( 0 == m_WorkThreads.Count() );
	}

	Assert( 0 == m_cThreadsRunning );

	// WARNING: We need to release any items left in the queues
	CWorkItem *pWorkItem = NULL;
	if ( m_pTSQueueCompleted->Count() > 0 )
	{
		EmitWarning( SPEW_THREADS, 2, "CWorkThreadPool::~CWorkThreadPool: work complete queue not empty, %d items discarded.\n", m_pTSQueueCompleted->Count() );
		pWorkItem = NULL;
		while ( m_pTSQueueCompleted->PopItem( &pWorkItem ) )
		{
			while( pWorkItem->Release() )
			{
				/* nothing */
			}
		}
	}

	if ( m_pTSQueueToProcess->Count() > 0 )
	{
		EmitWarning( SPEW_THREADS, 2, "CWorkThreadPool::~CWorkThreadPool: work processing queue not empty: %d items discarded.\n", m_pTSQueueToProcess->Count() );
		while ( m_pTSQueueToProcess->PopItem( &pWorkItem ) )
		{
			while( pWorkItem->Release() )
			{
				/* nothing */
			}
		}
	}

	delete m_pTSQueueToProcess;
	delete m_pTSQueueCompleted;
}


#if 0
//-----------------------------------------------------------------------------
// Purpose:	estimate the current backlog time using previous execution time,
//			the number of outstanding items, and the number of running threads
//-----------------------------------------------------------------------------
uint64 CWorkThreadPool::GetCurrentBacklogTime() const
{
	if ( m_WorkThreads.Count() == 0 )
		return 0;
	return ( m_pTSQueueToProcess->Count() * m_StatExecutionTime.GetUlAvg() ) / m_WorkThreads.Count();
}
#endif


int CWorkThreadPool::AddWorkThread( CWorkThread *pThread ) 
{
	AUTO_LOCK( m_WorkThreadMutex );
	Assert( pThread );
	return m_WorkThreads.AddToTail( pThread );
}


void CWorkThreadPool::StartWorkThread( CWorkThread *pWorkThread, int iName )
{
	char rgchThreadName[32];
	Q_snprintf( rgchThreadName, sizeof( rgchThreadName ), "%s:%d", m_szThreadNamePfx, iName );
	pWorkThread->SetName( rgchThreadName );
	if ( !pWorkThread->Start() )
		EmitError( SPEW_THREADS, "CWorkThreadPool::StartWorkThread: Thread creation failed.\n" );
}


void CWorkThreadPool::StartWorkThreads()
{
	m_bThreadsInitialized = true;
	if ( 0 == m_WorkThreads.Count() )
	{
		EmitWarning( SPEW_THREADS, 2, "CWorkThreadPool::StartWorkThreads: called with no threads in the pool, this is probably a bug.\n" );
		return;
	}
	m_bExiting = false;
	m_cThreadsRunning = 0;
	AUTO_LOCK( m_WorkThreadMutex );
	FOR_EACH_VEC( m_WorkThreads, i )
	{
		StartWorkThread( m_WorkThreads[i], i );
	}

	// XXX why?
	while ( m_cThreadsRunning == (uint) 0 )
	{
		ThreadSleep( 1 );
	}
}


//-----------------------------------------------------------------------------
// Purpose: stops whatever work threads we're running
//			this must be called before the thread pool object is destroyed
//-----------------------------------------------------------------------------
void CWorkThreadPool::StopWorkThreads()
{
	// indicate that we're shutting down;
	// don't accept more work in this thread
	m_bExiting = true;

	AUTO_LOCK( m_WorkThreadMutex );

	FOR_EACH_VEC( m_WorkThreads, i )
	{
		m_WorkThreads[i]->m_bExitThread = true;
		m_WorkThreads[i]->Cancel();
	}

	// loop until all threads are dead
	while ( true )
	{
		// This thread already holds the mutex; recursive try-lock should always succeed
		DbgVerify( BTryDeleteExitedWorkerThreads() );

		if ( m_WorkThreads.Count() == 0 )
			break;

		// Keep waking up threads until they're all dead.
		m_EventNewWorkItem.Set();
		
#ifdef _PS3
		// call to abort any running call to gethostbyname().
		// this is called over all the remaining work threads, while
		// waiting for the rest of the work threads to finish so that they won't
		// spuriously block on new calls to gethostbyname() as the
		// sys_net_abort_resolver call only stops the next call to the 
		// network API, not any future calls.

		FOR_EACH_VEC( m_WorkThreads, iPS3 )
		{
			// PS3 hack to abort gethostbyname() calls that may be blocking...
			sys_net_abort_resolver( m_WorkThreads[ iPS3 ]->GetThreadID(), SYS_NET_ABORT_STRICT_CHECK );
		}
#endif

		const uint k_uJoinTimeoutMillisec = 10000; // 10 seconds seems pretty arbitrary.

		CWorkThread *pWorkThread = m_WorkThreads[0];
		bool bJoined = pWorkThread->Join( k_uJoinTimeoutMillisec );
		if ( !bJoined )
		{
			// Print thread id as a pointer for cross-platform compatibility
			EmitWarning( SPEW_THREADS, 2, "Thread \"%s\" (ID %p) failed to shut down", pWorkThread->GetName(), (void*)pWorkThread->GetThreadID() );
		}
		else
		{
			// Succesful join means that the thread has terminated.
			if ( !pWorkThread->m_bFinished )
			{
				// This would be a logic error in the thread proc if it ever tripped.
				AssertMsg( false, "pWorkThread->m_bFinished is false but thread is not running" );
				// Recover by flagging the thread as potentially eligable for deletion, since it's dead.
				pWorkThread->m_bFinished = true;
			}
		}
	}

	Assert( m_WorkThreads.Count() == 0 && m_cThreadsRunning == (uint32) 0 );
}


//-----------------------------------------------------------------------------
// Purpose: sees if we have a non-zero number of work threads,
//			or a non-zero number of active threads
//-----------------------------------------------------------------------------
bool CWorkThreadPool::HasWorkItemsToProcess() const
{
	return ( m_pTSQueueToProcess->Count() > 0 ) 
 		|| ( m_cActiveThreads > 0 );
}


//-----------------------------------------------------------------------------
// Purpose: sets dynamic thread construction
//-----------------------------------------------------------------------------
void CWorkThreadPool::SetWorkThreadAutoConstruct( int cMaxThreads, IWorkThreadFactory *pWorkThreadConstructor )
{
	AUTO_LOCK( m_WorkThreadMutex );

	m_bThreadsInitialized = true;
	m_bAutoCreateThreads = true;
	m_cMaxThreads = MAX( 1, cMaxThreads );
	m_pWorkThreadConstructor = pWorkThreadConstructor;

	// If we have too many threads now, mark some to exit next time they loop.
	for ( int i = m_cMaxThreads; i < m_WorkThreads.Count(); i++ )
	{
		m_WorkThreads[i]->m_bExitThread = true;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Adds a work item
// Output:	true if successful,
//			false if a low priority work item is not added due to a busy system
//			false if this work pool is shutting down and work isn't being accepted
// NOTE:	Adding normal priority items should always succeed
//-----------------------------------------------------------------------------
bool CWorkThreadPool::AddWorkItem( CWorkItem *pWorkItem )
{
	Assert( !m_bExiting );
	if ( m_bExiting )
		return false;

	if ( m_bEnsureOutputOrdering )
	{
		AssertMsg( pWorkItem->m_bResubmit == false, "CWorkThreadPool can't support item auto resubmission when ensuring output ordering" );
	}

	// if we're in auto-create mode, make sure we have enough threads running
	if ( m_bAutoCreateThreads && m_WorkThreads.Count() < m_cMaxThreads )
	{
		int cPendingItems = m_pTSQueueToProcess->Count();

		// we shouldn't get more than 12 items queued per already existing thread, otherwise we
		// want to create a new thread to help us keep up.
		if ( m_WorkThreads.Count() < 1 || m_WorkThreads.Count() * 12 < ( cPendingItems + 1 ) )
		{
			if ( m_WorkThreads.Count() >= 2 && !m_LimitTimerCreateNewThreads.BLimitReached() )
			{
				// Don't create more yet, we don't want to create them too fast
			}
			else
			{
				// create another thread
				CWorkThread *pWorkThread = NULL;
				if ( m_pWorkThreadConstructor )
				{
					pWorkThread = m_pWorkThreadConstructor->CreateWorkerThread( this );
				}
				else
				{
					pWorkThread = new CWorkThread( this );
				}
				if( pWorkThread != NULL ) 
				{
					int iName = AddWorkThread( pWorkThread );
					StartWorkThread( pWorkThread, iName );
				}
				m_LimitTimerCreateNewThreads.SetLimit( 250*k_nThousand );
			}
		}
	}

	//
	//	Do we actually have any threads ?   If creating threads can fail, then maybe we don't !
	//	In that case, this WorkItem is not going to run !
	//
	if ( m_WorkThreads.Count() == 0 ) 
	{
		Assert(false);
		return	false ; 
	}
	

	// WARNING: We need to call pWorkItem AddRef() and Release() at all entry/exit points for the thread pool system.
	pWorkItem->AddRef();

	pWorkItem->m_ulSequenceNumber = (++m_ulLastUsedSequenceNumber);
	m_pTSQueueToProcess->PushItem( pWorkItem );

	if ( !BNeverSetEventOnAdd() && m_cActiveThreads == 0 )
	{
		VPROF_BUDGET( "SetEvent()", VPROF_BUDGETGROUP_THREADINGMAIN );
		m_EventNewWorkItem.Set();
	}

	return true;
}


CWorkItem *CWorkThreadPool::GetNextCompletedWorkItem( )
{
	CWorkItem *pWorkItem = NULL;

	// Use a while loop just in case ref counts get screwed up and an item gets deleted when we release our reference to it
	while ( m_pTSQueueCompleted->PopItem( &pWorkItem ) )
	{
		// WARNING: We need to call workitem AddRef() and Release() at all entry/exit points for the thread pool system.
		// Release() returns the current refcount of the object (after decrementing it by one) and should be non-zero unless the
		// the caller has released it already.
		if ( pWorkItem != NULL && pWorkItem->Release() > 0 )
		{
			return pWorkItem;
		}
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: gets the next work item to process. This non-blocking function
//			returns NULL immediately if there's nothing left in the queue.
//			otherwise, a pointer to the next CWorkItem.
//-----------------------------------------------------------------------------
CWorkItem *CWorkThreadPool::GetNextWorkItemToProcess( )
{
	CWorkItem *pWorkItem = NULL;

	if ( m_pTSQueueToProcess->Count() && m_pTSQueueToProcess->PopItem( &pWorkItem ) )
	{
		return pWorkItem;
	}
	
	return NULL;
}


bool CWorkThreadPool::BDispatchCompletedWorkItems( const CLimitTimer &limitTimer, CJobMgr *pJobMgr )
{
	BTryDeleteExitedWorkerThreads();

	CWorkItem *pWorkItem = GetNextCompletedWorkItem( );
	while ( pWorkItem != NULL )
	{
		uint64 ulSequenceNumber = pWorkItem->m_ulSequenceNumber;
		// NOTE: despite its name, this YIELDS - the target job
		// is resumed, and we resume here.
		if ( !pWorkItem->DispatchCompletedWorkItem( pJobMgr ) )
		{
			EmitWarning( SPEW_THREADS, 2, "Work Item for Work Pool %s completed but job no longer existed to notify\n", m_szThreadNamePfx == NULL ? "UNKNOWN" :m_szThreadNamePfx );
			AssertMsg1( m_bMayHaveJobTimeouts, "Work Item for Work Pool %s completed but job no longer existed to notify", m_szThreadNamePfx == NULL ? "UNKNOWN" :m_szThreadNamePfx );
		}

		// pWorkItem was released by DispatchCompletedWorkItem
		m_ulLastDispatchedSequenceNumber = ulSequenceNumber;
		if ( limitTimer.BLimitReached() )
			break;

		pWorkItem = GetNextCompletedWorkItem( );
	}

	return ( GetCompletedWorkItemCount() > 0 ); 	
}


//-----------------------------------------------------------------------------
// Purpose:	delete any thread objects that have exited
//			we'll make sure the thread has actually ended;
//			if they haven't, they'll remain in the threads to delete list
//-----------------------------------------------------------------------------
bool CWorkThreadPool::BTryDeleteExitedWorkerThreads()
{
	if ( m_WorkThreadMutex.TryLock() )
	{
		if ( m_cThreadsRunning < (uint) m_WorkThreads.Count() )
		{
			FOR_EACH_VEC_BACK( m_WorkThreads, i )
			{
				CWorkThread *pWorkThread = m_WorkThreads[i];
				if ( pWorkThread->m_bFinished && !pWorkThread->IsThreadRunning() )
				{
					m_WorkThreads.FastRemove( i );
					delete pWorkThread;
				}
			}
		}
		m_WorkThreadMutex.Unlock();
		return true;
	}
	return false;
}


bool CWorkItem::DispatchCompletedWorkItem( CJobMgr *pJobMgr )
{
	// Check if this work item needs to signal a job
	if ( pJobMgr && k_GIDNil != m_JobID )
	{
		if ( !pJobMgr->BRouteWorkItemCompletedIfExists( m_JobID, m_bCanceled ) )
			return false;
	}
	else if ( k_GIDNil != m_JobID )
	{
		// This should never happen since we have already released our reference to the work item
		// and the calling job should have released its ref when it exited
		AssertMsg( false, "CWorkItem::DispatchCompletedWorkItem: got a work item with no job ID" );
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose:	Called by the worker thread when it finishes an individual work item
//			This function will see if our work is meant to be well-ordred; if so,
//			it will do the necessary work to ensure ordering.
//
//			It adds the item to the completed work item list so 
//			the pool owner can retrieve it and checks to see if any threads
//			deserve to be shut down.
//-----------------------------------------------------------------------------
void CWorkThreadPool::OnWorkItemCompleted( CWorkItem *pWorkItem ) 
{ 
	if ( sm_pWorkItemsCompletedSignal != NULL )
		sm_pWorkItemsCompletedSignal->Signal();

	if ( !m_bEnsureOutputOrdering )
	{
		// Since we aren't locking this sequence number could get screwed up a bit, but it's 
		// pretty meaningless if ensure output ordering if off anyway...
		m_ulLastCompletedSequenceNumber = pWorkItem->m_ulSequenceNumber;
		m_pTSQueueCompleted->PushItem( pWorkItem ); 
	}
	else
	{
		// In the ordered case we need to lock completely here since we'll be moving around between
		// various data structures and also need to ensure the ordering of items in the TS queue
		m_MutexOnItemCompletedOrdered.Lock();
		if ( m_ulLastCompletedSequenceNumber + 1 == pWorkItem->m_ulSequenceNumber )
		{
			m_ulLastCompletedSequenceNumber = pWorkItem->m_ulSequenceNumber;
			m_pTSQueueCompleted->PushItem( pWorkItem ); 

			// We walk the vector multiple times, but it should be very short as items are likely to come in
			// close to in order, just mixed up a little if we have lots of threads or one item is much more
			// costly than others.  
			bool bFoundNext = false;
			do 
			{
				bFoundNext = false;
				FOR_EACH_VEC( m_vecCompletedAndWaiting, i )
				{
					CWorkItem *pWaiting = m_vecCompletedAndWaiting[i];
					if ( m_ulLastCompletedSequenceNumber + 1 == pWaiting->m_ulSequenceNumber )
					{
						m_ulLastCompletedSequenceNumber = pWaiting->m_ulSequenceNumber;
						m_pTSQueueCompleted->PushItem( pWaiting ); 
						m_vecCompletedAndWaiting.FastRemove( i );
						bFoundNext = true;
						break;
					}
				}
			} while ( bFoundNext == true );
		}
		else
		{
			m_vecCompletedAndWaiting.AddToTail( pWorkItem );
		}
		m_MutexOnItemCompletedOrdered.Unlock();
	}
}


//-----------------------------------------------------------------------------
// Purpose: return the count of items we've queued to process
//-----------------------------------------------------------------------------
int CWorkThreadPool::GetWorkItemToProcessCount() const
{
	return m_pTSQueueToProcess->Count();
}


//-----------------------------------------------------------------------------
// Purpose: return the count of items we've completed but not notified the consumer about
//-----------------------------------------------------------------------------
int CWorkThreadPool::GetCompletedWorkItemCount() const 
{ 
	int nCount = m_pTSQueueCompleted->Count();
	return nCount; 
}


#ifdef DBGFLAG_VALIDATE
//-----------------------------------------------------------------------------
// Purpose: Validates memory
//-----------------------------------------------------------------------------
void CWorkThreadPool::Validate( CValidator &validator, const char *pchName )	
{
	VALIDATE_SCOPE();
	AUTO_LOCK( m_WorkThreadMutex );

	ValidateObj( m_WorkThreads );
	FOR_EACH_VEC( m_WorkThreads, iWorkThread )
	{
		m_WorkThreads[ iWorkThread ]->Suspend();
		ValidatePtr( m_WorkThreads[ iWorkThread ] );
	}

	ValidateAlignedPtr( m_pTSQueueCompleted );
	ValidateAlignedPtr( m_pTSQueueToProcess );
	ValidateObj( m_vecCompletedAndWaiting );
	FOR_EACH_VEC( m_vecCompletedAndWaiting, j )
	{
		ValidatePtr( m_vecCompletedAndWaiting.Element( j ) );
	}

	FOR_EACH_VEC( m_WorkThreads, iWorkThread )
	{
		m_WorkThreads[ iWorkThread ]->Resume();
	}

#if 0
	ValidateObj( m_StatExecutionTime );
	ValidateObj( m_StatWaitTime );
#endif
}
#endif // DBGFLAG_VALIDATE

} // namespace GCSDK