//------------------------------------------------------------------------------
// Desc:	Functions for creating, starting, stopping, controlling threads.
//
// Tabs:	3
//
//		Copyright (c) 2000-2006 Novell, Inc. All Rights Reserved.
//
//		This program is free software; you can redistribute it and/or
//		modify it under the terms of version 2 of the GNU General Public
//		License as published by the Free Software Foundation.
//
//		This program is distributed in the hope that it will be useful,
//		but WITHOUT ANY WARRANTY; without even the implied warranty of
//		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//		GNU General Public License for more details.
//
//		You should have received a copy of the GNU General Public License
//		along with this program; if not, contact Novell, Inc.
//
//		To contact Novell about this file by physical or electronic mail,
//		you may find current contact information at www.novell.com
//
// $Id: ftkthrd.cpp 3115 2006-01-19 13:24:39 -0700 (Thu, 19 Jan 2006) dsanders $
//------------------------------------------------------------------------------

#include "ftksys.h"

#ifdef FLM_UNIX
	pid_t getpid( void);
#endif

#ifdef FLM_LIBC_NLM
	void * threadStub(
		void *	pvThread);
#elif defined( FLM_RING_ZERO_NLM)
	void * threadStub(
		void *	pvUnused,
		void *	pvThread);
#elif defined( FLM_WIN)
	unsigned __stdcall threadStub(
		void *	pvThread);
#elif defined( FLM_UNIX)
	extern "C" void * threadStub(
		void *	pvThread);
#endif

/****************************************************************************
Desc:
****************************************************************************/
class F_ThreadMgr : public IF_ThreadMgr
{
public:

	F_ThreadMgr()
	{
		m_hMutex = F_MUTEX_NULL;
		m_pThreadList = NULL;
		m_uiNumThreads = 0;
		m_groupCounter = 0;
	}

	virtual ~F_ThreadMgr();

	RCODE FLMAPI setupThreadMgr( void);

	FLMINT FLMAPI AddRef( void)
	{
		return( f_atomicInc( &m_refCnt));
	}

	FLMINT FLMAPI Release( void)
	{
		FLMINT		iRefCnt = f_atomicDec( &m_refCnt);
		
		if( !iRefCnt)
		{
			delete this;
		}
		
		return( iRefCnt);
	}
	
	RCODE FLMAPI createThread(
		IF_Thread **		ppThread,
		F_THREAD_FUNC		fnThread,
		const char *		pszThreadName,
		FLMUINT				uiThreadGroup,
		FLMUINT				uiAppId,
		void *				pvParm1,
		void *				pvParm2,
		FLMUINT				uiStackSize);
		
	void FLMAPI shutdownThreadGroup(
		FLMUINT				uiThreadGroup);

	void FLMAPI setThreadShutdownFlag(
		FLMUINT				uiThreadId);

	RCODE FLMAPI findThread(
		IF_Thread **		ppThread,
		FLMUINT				uiThreadGroup,
		FLMUINT				uiAppId = 0,
		FLMBOOL				bOkToFindMe = TRUE);

	RCODE FLMAPI getNextGroupThread(
		IF_Thread **		ppThread,
		FLMUINT				uiThreadGroup,
		FLMUINT *			puiThreadId);

	RCODE FLMAPI getThreadInfo(
		F_Pool *				pPool,
		F_THREAD_INFO **	ppThreadInfo,
		FLMUINT *			puiNumThreads);

	RCODE FLMAPI getThreadName(
		FLMUINT				uiThreadId,
		char *				pszThreadName,
		FLMUINT *			puiLength);
	
	FLMUINT FLMAPI getThreadGroupCount(
		FLMUINT				uiThreadGroup);
		
	FLMUINT FLMAPI allocGroupId( void);
		
	inline void lockMutex( void)
	{
		f_mutexLock( m_hMutex);
	}
	
	inline void unlockMutex( void)
	{
		f_mutexUnlock( m_hMutex);
	}

	void unlinkThread(
		IF_Thread *		pThread,
		FLMBOOL			bMutexLocked);

	RCODE getThread(
		FLMUINT				uiThreadId,
		F_Thread **			ppThread);
	
private:

	F_MUTEX			m_hMutex;
	F_Thread *		m_pThreadList;
	FLMUINT			m_uiNumThreads;
	FLMATOMIC		m_groupCounter;

friend class F_Thread;
};

/****************************************************************************
Desc:
****************************************************************************/
class F_Thread : public IF_Thread
{
public:

	F_Thread()
	{
		m_hMutex = F_MUTEX_NULL;
		m_pszThreadName = NULL;
		m_pszThreadStatus = NULL;
		m_uiStatusBufLen = 0;
		m_uiThreadGroup = F_INVALID_THREAD_GROUP;
		m_pPrev = NULL;
		m_pNext = NULL;
		cleanupThread();
	}

	virtual ~F_Thread()
	{
		stopThread();
		cleanupThread();
	}

	FLMINT FLMAPI AddRef( void);

	FLMINT FLMAPI Release( void);

	RCODE FLMAPI startThread(
		F_THREAD_FUNC	fnThread,
		const char *	pszThreadName,
		FLMUINT			uiThreadGroup,
		FLMUINT			uiAppId,
		void *			pvParm1,
		void *			pvParm2,
		FLMUINT        uiStackSize);

	void FLMAPI stopThread( void);

	FINLINE FLMUINT FLMAPI getThreadId( void)
	{
		return( m_uiThreadId);
	}

	FINLINE FLMBOOL FLMAPI getShutdownFlag( void)
	{
		return( m_bShutdown);
	}

	FINLINE RCODE FLMAPI getExitCode( void)
	{
		return( m_exitRc);
	}

	FINLINE void * FLMAPI getParm1( void)
	{
		return( m_pvParm1);
	}

	FINLINE void FLMAPI setParm1(
		void *		pvParm)
	{
		m_pvParm1 = pvParm;
	}

	FINLINE void * FLMAPI getParm2( void)
	{
		return( m_pvParm2);
	}

	FINLINE void FLMAPI setParm2(
		void *		pvParm)
	{
		m_pvParm2 = pvParm;
	}

	FINLINE void FLMAPI setShutdownFlag( void)
	{
		m_bShutdown = TRUE;
	}

	FINLINE FLMBOOL FLMAPI isThreadRunning( void)
	{
		return( m_bRunning);
	}

	void FLMAPI setThreadStatusStr(
		const char *	pszStatus);

	void FLMAPI setThreadStatus(
		const char *	pszBuffer, ...);

	void FLMAPI setThreadStatus(
		eThreadStatus	genericStatus);

	FINLINE void FLMAPI setThreadAppId(
		FLMUINT		uiAppId)
	{
		f_mutexLock( m_hMutex);
		m_uiAppId = uiAppId;
		f_mutexUnlock( m_hMutex);
	}

	FINLINE FLMUINT FLMAPI getThreadAppId( void)
	{
		return( m_uiAppId);
	}

	FINLINE FLMUINT FLMAPI getThreadGroup( void)
	{
		return( m_uiThreadGroup);
	}

	void FLMAPI cleanupThread( void);

	void FLMAPI sleep(
			FLMUINT		uiMilliseconds);
			
	void FLMAPI waitToComplete( void);

	F_MUTEX				m_hMutex;
	F_Thread *			m_pPrev;
	F_Thread *			m_pNext;
	char *				m_pszThreadName;
	char *				m_pszThreadStatus;
	FLMUINT				m_uiStatusBufLen;
	FLMBOOL				m_bShutdown;
	F_THREAD_FUNC		m_fnThread;
	FLMBOOL				m_bRunning;
	FLMUINT				m_uiStackSize;
	void *				m_pvParm1;
	void *				m_pvParm2;
	FLMUINT				m_uiThreadId;
	FLMUINT				m_uiThreadGroup;
	FLMUINT				m_uiAppId;
	FLMUINT				m_uiStartTime;
	RCODE					m_exitRc;

friend class F_ThreadMgr;
};

/****************************************************************************
Desc:
****************************************************************************/
class F_ThreadInfo : public IF_ThreadInfo
{
public:

	F_ThreadInfo()
	{
		m_pool.poolInit( 512);
		m_uiNumThreads = 0;
		m_pThreadInfoArray = NULL;
	}

	virtual ~F_ThreadInfo()
	{
		m_pool.poolFree();
	}

	FLMUINT FLMAPI getNumThreads( void)
	{
		return( m_uiNumThreads);
	}

	FINLINE void FLMAPI getThreadInfo(
		FLMUINT				uiThreadNum,
		FLMUINT *			puiThreadId,
		FLMUINT *			puiThreadGroup,
		FLMUINT *			puiAppId,
		FLMUINT *			puiStartTime,
		const char **		ppszThreadName,
		const char **		ppszThreadStatus)
	{
		if (uiThreadNum < m_uiNumThreads)
		{
			F_THREAD_INFO *	pThrdInfo = &m_pThreadInfoArray [uiThreadNum];

			*puiThreadId = pThrdInfo->uiThreadId;
			*puiThreadGroup = pThrdInfo->uiThreadGroup;
			*puiAppId = pThrdInfo->uiAppId;
			*puiStartTime = pThrdInfo->uiStartTime;
			*ppszThreadName = pThrdInfo->pszThreadName;
			*ppszThreadStatus = pThrdInfo->pszThreadStatus;
		}
		else
		{
			*puiThreadId = 0;
			*puiThreadGroup = 0;
			*puiAppId = 0;
			*puiStartTime = 0;
			*ppszThreadName = NULL;
			*ppszThreadStatus = NULL;
		}
	}

	F_Pool				m_pool;
	F_THREAD_INFO *	m_pThreadInfoArray;
	FLMUINT				m_uiNumThreads;
};

/****************************************************************************
Desc:
****************************************************************************/
RCODE f_allocThreadMgr(
	IF_ThreadMgr **		ppThreadMgr)
{
	RCODE				rc = NE_FLM_OK;
	F_ThreadMgr *	pThreadMgr = NULL;

	if( (pThreadMgr = f_new F_ThreadMgr) == NULL)
	{
		rc = RC_SET( NE_FLM_MEM);
	}

	if( RC_BAD( rc = pThreadMgr->setupThreadMgr()))
	{
		goto Exit;
	}

	*ppThreadMgr = pThreadMgr;
	pThreadMgr = NULL;

Exit:

	if( pThreadMgr)
	{
		pThreadMgr->Release();
	}
	
	return( rc);
}

/****************************************************************************
Desc:
****************************************************************************/
RCODE FLMAPI FlmGetThreadMgr(
	IF_ThreadMgr **		ppThreadMgr)
{
	*ppThreadMgr = f_getThreadMgrPtr();
	(*ppThreadMgr)->AddRef();
	return( NE_FLM_OK);
}
		
/****************************************************************************
Desc:	Add a Reference to this object.
****************************************************************************/
FLMINT FLMAPI F_Thread::AddRef( void)
{
	return( f_atomicInc( &m_refCnt));
}

/****************************************************************************
Desc: Removes a reference to this object.
****************************************************************************/
FLMINT FLMAPI F_Thread::Release( void)
{
	FLMINT		iRefCnt = f_atomicDec( &m_refCnt);
	
	if( !iRefCnt)
	{
		delete this;
	}

	return( iRefCnt);
}

/****************************************************************************
Desc:    Performs various setup work and starts a new thread
****************************************************************************/
RCODE FLMAPI F_Thread::startThread(
	F_THREAD_FUNC	fnThread,
	const char *	pszThreadName,
	FLMUINT			uiThreadGroup,
	FLMUINT			uiAppId,
	void *			pvParm1,
	void *			pvParm2,
	FLMUINT        uiStackSize)
{
	RCODE						rc = NE_FLM_OK;
	F_ThreadMgr *			pThreadMgr = (F_ThreadMgr *)f_getThreadMgrPtr();
	FLMBOOL					bManagerMutexLocked = FALSE;
#ifdef FLM_LIBC_NLM
	pthread_attr_t			thread_attr;
	pthread_t				uiThreadId;
#endif
#ifdef FLM_RING_ZERO_NLM
	void *					hThread = NULL;
#endif
#ifdef FLM_WIN
	unsigned					uiThreadId;
#endif
#if defined( FLM_UNIX)
	pthread_attr_t			thread_attr;
	pthread_t				uiThreadId;
#endif

	f_assert( fnThread != NULL && m_fnThread == NULL);
	f_assert( uiThreadGroup != F_INVALID_THREAD_GROUP);

	m_fnThread = fnThread;
	m_pvParm1 = pvParm1;
	m_pvParm2 = pvParm2;

	// Initialize the thread's mutex

	if( RC_BAD( rc = f_mutexCreate( &m_hMutex)))
	{
		goto Exit;
	}

	// Set the stack size

	m_uiStackSize = (uiStackSize < F_THREAD_MIN_STACK_SIZE)
							? F_THREAD_MIN_STACK_SIZE
							: uiStackSize;

	// Set the thread name

	if( pszThreadName && *pszThreadName)
	{
		FLMUINT		uiNameLen = f_strlen( pszThreadName) + 1;

		if( RC_BAD( rc = f_alloc( uiNameLen, &m_pszThreadName)))
		{
			goto Exit;
		}

		f_memcpy( (void *)m_pszThreadName, pszThreadName, uiNameLen);
	}

	// Set the thread group ID and the application-specified thread ID

	m_uiThreadGroup = uiThreadGroup;
	m_uiAppId = uiAppId;

	// Set the thread's state to "running" -- if we fail to
	// start the thread, this will be set back to false when
	// the cleanupThread() method is called below.  We set this
	// to TRUE here so that the stopThread() method won't get
	// stuck in an infinite loop if the thread was never started.

	m_bRunning = TRUE;

	// Lock the thread manager's mutex.

	f_mutexLock( pThreadMgr->m_hMutex);
	bManagerMutexLocked = TRUE;

	// Increment the active thread count

	pThreadMgr->m_uiNumThreads++;

	// Link the thread into the manager's list.  We can't link threads in order
	// by thread ID at this point, because we don't know what the new thread's
	// ID will be.

	if( pThreadMgr->m_pThreadList)
	{
		pThreadMgr->m_pThreadList->m_pPrev = this;
	}

	m_pNext = pThreadMgr->m_pThreadList;
	pThreadMgr->m_pThreadList = this;

	// Increment the reference count of the thread object now
	// that it is linked into the thread manager's list.

	m_refCnt++;

	// Start the thread

#ifdef FLM_WIN
	if( _beginthreadex(
		NULL, (unsigned int)m_uiStackSize, threadStub,
		(void *)this, 0, &uiThreadId) == 0)
	{
		rc = RC_SET( NE_FLM_COULD_NOT_START_THREAD);
		goto Exit;
	}
	m_uiThreadId = (FLMUINT)uiThreadId;
#elif defined( FLM_LIBC_NLM)
		pthread_attr_init( &thread_attr);
		pthread_attr_setdetachstate( &thread_attr, PTHREAD_CREATE_DETACHED);

		if (pthread_create( &uiThreadId, &thread_attr,
				threadStub, this) != 0)
		{
			rc = RC_SET( NE_FLM_COULD_NOT_START_THREAD);
			goto Exit;
		}

		m_uiThreadId = (FLMUINT)uiThreadId;
		pthread_attr_destroy( &thread_attr);
#elif defined( FLM_RING_ZERO_NLM)
	if( (hThread = kCreateThread( 
		(BYTE *)((m_pszThreadName)
			? (BYTE *)m_pszThreadName 
			: (BYTE *)"FTK"),
		threadStub, NULL, (LONG)m_uiStackSize,
		(void *)this)) == NULL)
	{
		rc = RC_SET( NE_FLM_COULD_NOT_START_THREAD);
		goto Exit;
	}
	m_uiThreadId = (FLMUINT)hThread;

	if( kSetThreadLoadHandle( hThread, (LONG)f_getNLMHandle()) != 0)
	{
		(void)kDestroyThread( hThread);
		rc = RC_SET( NE_FLM_COULD_NOT_START_THREAD);
		goto Exit;
	}
			
   if( kScheduleThread( hThread) != 0)
	{
		(void)kDestroyThread( hThread);
		rc = RC_SET( NE_FLM_COULD_NOT_START_THREAD);
		goto Exit;
	}
#elif defined( FLM_UNIX)
	pthread_attr_init( &thread_attr);
	pthread_attr_setdetachstate( &thread_attr, PTHREAD_CREATE_DETACHED);

	if (pthread_create( &uiThreadId, &thread_attr,
			threadStub, this) != 0)
	{
		rc = RC_SET( NE_FLM_COULD_NOT_START_THREAD);
		goto Exit;
	}

	m_uiThreadId = (FLMUINT)uiThreadId;
	pthread_attr_destroy( &thread_attr);
#endif

	// Code is not designed to handle a thread ID of 0

	f_assert( m_uiThreadId != 0);

	// Unlock the thread manager's mutex.

	f_mutexUnlock( pThreadMgr->m_hMutex);
	bManagerMutexLocked = FALSE;

Exit:

	if( RC_BAD( rc))
	{
		// Unlink the thread from the manager's list.  This call
		// won't do anything if the thread was not linked above.

		pThreadMgr->unlinkThread( this, bManagerMutexLocked);

		// Reset the thread object back to its initial state

		cleanupThread();
	}

	if( bManagerMutexLocked)
	{
		f_mutexUnlock( pThreadMgr->m_hMutex);
	}

	return( rc);
}

/****************************************************************************
Desc: Stop a running thread
****************************************************************************/
void FLMAPI F_Thread::stopThread( void)
{
	// Set the shutdown flag and wait for the thread's
	// status to be something other than "running"

	m_bShutdown = TRUE;
	while( m_bRunning)
	{
		f_sleep( 10);
	}

	// Reset the shutdown flag in case this object is re-used.

	m_bShutdown = FALSE;
}

/****************************************************************************
Desc:
****************************************************************************/
void FLMAPI F_Thread::waitToComplete( void)
{
	while( m_bRunning)
	{
		f_sleep( 10);
	}
}

/****************************************************************************
Desc:    Begins a new thread of execution and calls the passed function.
			Performs generic thread init and cleanup functions.
****************************************************************************/
#ifdef FLM_LIBC_NLM
void * threadStub(
	void *	pvThread)
#elif defined( FLM_RING_ZERO_NLM)
void * threadStub(
	void *	pvUnused,
	void *	pvThread)
#elif defined( FLM_WIN)
unsigned __stdcall threadStub(
	void *	pvThread)
#elif defined( FLM_UNIX)
void * threadStub(
	void *	pvThread)
#endif
{
	F_Thread *			pThread = (F_Thread *)pvThread;
	F_ThreadMgr *		pThreadMgr = (F_ThreadMgr *)f_getThreadMgrPtr();

#ifdef FLM_RING_ZERO_NLM
	F_UNREFERENCED_PARM( pvUnused);
#endif

#if defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
	// Block all signals (main thread will handle all signals)

	sigset_t mask;
	sigfillset(&mask);
	pthread_sigmask(SIG_SETMASK, &mask, 0);
#endif

	// Lock the manager's mutex

	pThreadMgr->lockMutex();

	// At this point, the thread ID must match.

	f_assert( pThread->m_uiThreadId == f_threadId());

	// Set the start time

	f_timeGetSeconds( &pThread->m_uiStartTime);

	// Unlock the manager's mutex

	pThreadMgr->unlockMutex();

	// Call the thread's function

	pThread->m_exitRc = pThread->m_fnThread( pThread);

	// Add a temporary reference to the thread object so
	// it doesn't go away when we unlink it from the
	// manager

	pThread->AddRef();

	// Unlink the thread from the thread manager.

	pThreadMgr->unlinkThread( pThread, FALSE);

	// Set the running flag to FALSE

	pThread->m_bRunning = FALSE;

	// Release the temporary reference to the thread.  Once the
	// reference is release, pThread must not be accessed because
	// the object may have gone away.

	pThread->Release();
	pThread = NULL;

	// Terminate the thread

#if defined( FLM_WIN)
	_endthreadex( 0);
	return( 0);
#elif defined( FLM_RING_ZERO_NLM)
	kExitThread( NULL);
#endif

#if defined( FLM_NLM) || defined( FLM_UNIX)
	return( NULL);
#endif
}

/****************************************************************************
Desc:    Frees any resources allocated to the thread and resets member
			variables to their initial state
****************************************************************************/
void FLMAPI F_Thread::cleanupThread( void)
{
	f_assert( !m_pPrev && !m_pNext);
	
	if( m_hMutex != F_MUTEX_NULL)
	{
		f_mutexDestroy( &m_hMutex);
	}

	if( m_pszThreadName)
	{
		f_free( &m_pszThreadName);
	}

	if( m_pszThreadStatus)
	{
		f_free( &m_pszThreadStatus);
	}

	m_uiStatusBufLen = 0;
	m_bShutdown = FALSE;
	m_fnThread = NULL;
	m_bRunning = FALSE;
	m_uiStackSize = 0;
	m_pvParm1 = NULL;
	m_pvParm2 = NULL;
	m_uiThreadId = 0;
	m_uiThreadGroup = F_INVALID_THREAD_GROUP;
	m_uiAppId = 0;
	m_uiStartTime = 0;
	m_exitRc = NE_FLM_OK;
}

/****************************************************************************
Desc:
****************************************************************************/
void FLMAPI F_Thread::sleep(
	FLMUINT		uiMilliseconds)
{
	FLMUINT		uiTimeToSleep;
	
	if( !uiMilliseconds)
	{
		f_yieldCPU();
		return;
	}
	
	while( uiMilliseconds && !m_bShutdown)
	{
		uiTimeToSleep = f_min( uiMilliseconds, 50);
		f_sleep( uiTimeToSleep);
		uiMilliseconds -= uiTimeToSleep;
	}
}
			
/****************************************************************************
Desc:    Set the thread's status
****************************************************************************/
void FLMAPI F_Thread::setThreadStatusStr(
	const char *		pszStatus)
{
	FLMUINT		uiStatusLen = f_strlen( pszStatus) + 1;

	if( m_uiStatusBufLen < uiStatusLen)
	{
		FLMUINT		uiAllocSize = uiStatusLen < 128 ? 128 : uiStatusLen;

		if( m_pszThreadStatus != NULL)
		{
			f_free( &m_pszThreadStatus);
		}
		m_uiStatusBufLen = 0;

		if( RC_BAD( f_alloc( uiAllocSize, &m_pszThreadStatus)))
		{
			goto Exit;
		}
		m_uiStatusBufLen = uiAllocSize;
	}

	f_mutexLock( m_hMutex);
	f_memcpy( m_pszThreadStatus, pszStatus, uiStatusLen);
	f_mutexUnlock( m_hMutex);

Exit:

	return;
}

/****************************************************************************
Desc:    Set the thread's status
****************************************************************************/
void FLMAPI F_Thread::setThreadStatus(
	const char *	pszFormat, ...)
{
	char				pucBuffer[ 128];
	f_va_list		args;

	f_va_start( args, pszFormat);
	f_vsprintf( pucBuffer, pszFormat, &args);
	f_va_end( args);

	setThreadStatusStr( pucBuffer);
}

/****************************************************************************
Desc:    Set the thread's status to a generic string
****************************************************************************/
void FLMAPI F_Thread::setThreadStatus(
	eThreadStatus	genericStatus)
{
	const char *	pszStatus = NULL;

	switch( genericStatus)
	{
		case FLM_THREAD_STATUS_INITIALIZING:
			pszStatus = "Initializing";
			break;

		case FLM_THREAD_STATUS_RUNNING:
			pszStatus = "Running";
			break;

		case FLM_THREAD_STATUS_SLEEPING:
			pszStatus = "Sleeping";
			break;

		case FLM_THREAD_STATUS_TERMINATING:
			pszStatus = "Terminating";
			break;

		case FLM_THREAD_STATUS_UNKNOWN:
		default:
			pszStatus = "Unknown";
			break;
	}

	if( pszStatus)
	{
		setThreadStatusStr( pszStatus);
	}
}

/****************************************************************************
Desc:
****************************************************************************/
F_ThreadMgr::~F_ThreadMgr()
{
	F_Thread *		pTmpThread;

	if( m_hMutex != F_MUTEX_NULL)
	{
		f_mutexLock( m_hMutex);
		pTmpThread = m_pThreadList;
		while( pTmpThread)
		{
			pTmpThread->setShutdownFlag();
			pTmpThread = pTmpThread->m_pNext;
		}

		while( m_pThreadList)
		{
			f_mutexUnlock( m_hMutex);
			f_sleep( 50);
			f_mutexLock( m_hMutex);
		}

		f_mutexUnlock( m_hMutex);
		f_mutexDestroy( &m_hMutex);
	}
}
/****************************************************************************
Desc:		Allocates resources needed by the thread manager
****************************************************************************/
RCODE FLMAPI F_ThreadMgr::setupThreadMgr( void)
{
	RCODE		rc = NE_FLM_OK;

	f_assert( m_hMutex == F_MUTEX_NULL);

	if( RC_BAD( rc = f_mutexCreate( &m_hMutex)))
	{
		goto Exit;
	}

Exit:

	return( rc);
}

/****************************************************************************
Desc:		Removes a thread from the thread manager's list
Notes:	This routine assumes that the manager's mutex is already locked.
****************************************************************************/
void F_ThreadMgr::unlinkThread(
	IF_Thread *		ifpThread,
	FLMBOOL			bMutexIsLocked)
{
	F_Thread *		pThread = (F_Thread *)ifpThread;
	
	// Lock the thread manager's mutex

	if( !bMutexIsLocked)
	{
		f_mutexLock( m_hMutex);
	}

	// If the thread isn't linked into the list,
	// don't do anything

	if( !pThread->m_pPrev && !pThread->m_pNext &&
		m_pThreadList != pThread)
	{
		goto Exit;
	}

	// Decrement the active thread count

	f_assert( m_uiNumThreads);
	m_uiNumThreads--;

	if( pThread->m_pPrev)
	{
		pThread->m_pPrev->m_pNext = pThread->m_pNext;
	}
	else
	{
		m_pThreadList = pThread->m_pNext;
	}

	if( pThread->m_pNext)
	{
		pThread->m_pNext->m_pPrev = pThread->m_pPrev;
	}

	pThread->m_pNext = NULL;
	pThread->m_pPrev = NULL;

	// Release the thread object

	pThread->Release();

Exit:

	if( !bMutexIsLocked)
	{
		f_mutexUnlock( m_hMutex);
	}
}

/****************************************************************************
Desc:		Signals all threads in a thread group to shut down and waits
			for them to terminate.
****************************************************************************/
void FLMAPI F_ThreadMgr::shutdownThreadGroup(
	FLMUINT		uiThreadGroup)
{
	F_Thread *		pThread;
	FLMUINT			uiCount;
	
	f_assert( uiThreadGroup != F_INVALID_THREAD_GROUP);

	for( ;;)
	{
		f_mutexLock( m_hMutex);

		uiCount = 0;
		pThread = m_pThreadList;
		while( pThread)
		{
			if( pThread->m_uiThreadGroup == uiThreadGroup)
			{
				pThread->setShutdownFlag();
				uiCount++;
			}
			pThread = pThread->m_pNext;
		}

		f_mutexUnlock( m_hMutex);

		if( !uiCount)
		{
			break;
		}

		// The threads will automatically unlink themselves from
		// the manager before they terminate.  Just sleep for
		// a few milliseconds and look through the list again to
		// verify that there are no more threads in the group.

		f_sleep( 200);
	}
}

/****************************************************************************
Desc:		Signals a thread to shut down.
****************************************************************************/
void FLMAPI F_ThreadMgr::setThreadShutdownFlag(
	FLMUINT			uiThreadId)
{
	F_Thread *		pThread;

	f_assert( uiThreadId != 0);

	f_mutexLock( m_hMutex);
	pThread = m_pThreadList;
	while( pThread)
	{
		if( pThread->m_uiThreadId == uiThreadId)
		{
			pThread->setShutdownFlag();
			break;
		}
		pThread = pThread->m_pNext;
	}

	f_mutexUnlock( m_hMutex);
}

/****************************************************************************
Desc:		Allocates an array of F_THREAD_INFO structures and populates them
			with information about the threads being managed by this object.
****************************************************************************/
RCODE FLMAPI F_ThreadMgr::getThreadInfo(
	F_Pool *				pPool,
	F_THREAD_INFO **	ppThreadInfo,
	FLMUINT *			puiNumThreads)
{
	RCODE					rc = NE_FLM_OK;
	FLMUINT				uiOffset;
	FLMUINT				uiLoop;
	FLMUINT				uiSubLoop;
	FLMUINT				uiLen;
	FLMBOOL				bMutexLocked = FALSE;
	F_THREAD_INFO *	pThreadInfo = NULL;
	F_THREAD_INFO		tmpThreadInfo;
	F_Thread *			pCurThread;
	void *				pvMark = pPool->poolMark();

	*ppThreadInfo = NULL;
	*puiNumThreads = 0;

	f_mutexLock( m_hMutex);
	bMutexLocked = TRUE;

	if( m_uiNumThreads == 0)
	{
		goto Exit;
	}

	if (RC_BAD( rc = pPool->poolCalloc( sizeof( F_THREAD_INFO) * m_uiNumThreads,
		(void **)&pThreadInfo)))
	{
		goto Exit;
	}

	uiOffset = 0;
	pCurThread = m_pThreadList;
	while( pCurThread)
	{
		f_assert( uiOffset < m_uiNumThreads);
		f_mutexLock( pCurThread->m_hMutex);

		pThreadInfo[ uiOffset].uiThreadId = pCurThread->m_uiThreadId;
		pThreadInfo[ uiOffset].uiThreadGroup = pCurThread->m_uiThreadGroup;
		pThreadInfo[ uiOffset].uiAppId = pCurThread->m_uiAppId;
		pThreadInfo[ uiOffset].uiStartTime = pCurThread->m_uiStartTime;

		if( pCurThread->m_pszThreadName)
		{
			uiLen = f_strlen( pCurThread->m_pszThreadName) + 1;

			if (RC_OK( pPool->poolCalloc( uiLen,
										(void **)&pThreadInfo[ uiOffset].pszThreadName)))
			{
				f_memcpy( (void *)pThreadInfo[ uiOffset].pszThreadName,
					pCurThread->m_pszThreadName, uiLen);
			}
		}

		if( pCurThread->m_pszThreadStatus)
		{
			uiLen = f_strlen( pCurThread->m_pszThreadStatus) + 1;

			if (RC_OK( pPool->poolCalloc( uiLen,
								(void **)&pThreadInfo[ uiOffset].pszThreadStatus)))
			{
				f_memcpy( (void *)(pThreadInfo[ uiOffset].pszThreadStatus),
					pCurThread->m_pszThreadStatus, uiLen);
			}
		}

		f_mutexUnlock( pCurThread->m_hMutex);
		uiOffset++;
		pCurThread = pCurThread->m_pNext;
	}

	f_assert( uiOffset == m_uiNumThreads);
	*puiNumThreads = m_uiNumThreads;

	f_mutexUnlock( m_hMutex);
	bMutexLocked = FALSE;

	// Sort the list by thread ID

	for( uiLoop = 0; uiLoop < *puiNumThreads; uiLoop++)
	{
		for( uiSubLoop = uiLoop + 1; uiSubLoop < *puiNumThreads; uiSubLoop++)
		{
			if( pThreadInfo[ uiLoop].uiThreadId >
				pThreadInfo[ uiSubLoop].uiThreadId)
			{
				f_memcpy( &tmpThreadInfo,
					&pThreadInfo[ uiLoop], sizeof( F_THREAD_INFO));
				f_memcpy( &pThreadInfo[ uiLoop],
					&pThreadInfo[ uiSubLoop], sizeof( F_THREAD_INFO));
				f_memcpy( &pThreadInfo[ uiSubLoop],
					&tmpThreadInfo, sizeof( F_THREAD_INFO));
			}
		}
	}

	*ppThreadInfo = pThreadInfo;

Exit:

	if( RC_BAD( rc))
	{
		pPool->poolReset( pvMark);
	}

	if( bMutexLocked)
	{
		f_mutexUnlock( m_hMutex);
	}

	return( rc);
}

/****************************************************************************
Desc:
****************************************************************************/
RCODE FLMAPI F_ThreadMgr::getThreadName(
	FLMUINT			uiThreadId,
	char *			pszThreadName,
	FLMUINT *		puiLength)
{
	RCODE				rc = NE_FLM_OK;
	F_Thread *		pThread = NULL;
	FLMUINT			uiCopyLen;
	
	if( RC_BAD( rc = getThread( uiThreadId, &pThread)))
	{
		goto Exit;
	}
	
	f_mutexLock( pThread->m_hMutex);

	if( pThread->m_pszThreadName)
	{
		uiCopyLen = f_min( *puiLength - 1, 
							f_strlen( pThread->m_pszThreadName));
		f_strncpy( pszThreadName, pThread->m_pszThreadName, uiCopyLen);
		*puiLength = uiCopyLen;
	}
	else
	{
		*pszThreadName = 0;
	}
			
	f_mutexUnlock( pThread->m_hMutex);
	
	
Exit:

	if( pThread)
	{
		pThread->Release();
	}
	
	return( rc);
}

/****************************************************************************
Desc:
****************************************************************************/
RCODE F_ThreadMgr::getThread(
	FLMUINT			uiThreadId,
	F_Thread **		ppThread)
{
	RCODE				rc = NE_FLM_OK;
	FLMBOOL			bMutexLocked = FALSE;
	F_Thread *		pCurThread;

	f_assert( *ppThread == NULL);
	
	f_mutexLock( m_hMutex);
	bMutexLocked = TRUE;

	pCurThread = m_pThreadList;
	while( pCurThread)
	{
		if( pCurThread->m_uiThreadId == uiThreadId)
		{
			*ppThread = pCurThread;
			pCurThread->AddRef();
			break;
		}

		pCurThread = pCurThread->m_pNext;
	}

	f_mutexUnlock( m_hMutex);
	bMutexLocked = FALSE;
	
	if( !pCurThread)
	{
		rc = RC_SET( NE_FLM_NOT_FOUND);
		goto Exit;
	}
	
Exit:

	if( bMutexLocked)
	{
		f_mutexUnlock( m_hMutex);
	}
	
	return( rc);
}
	
/****************************************************************************
Desc:		Finds a thread based on user-specified identifiers
****************************************************************************/
RCODE FLMAPI F_ThreadMgr::findThread(
	IF_Thread **	ppThread,
	FLMUINT			uiThreadGroup,
	FLMUINT			uiAppId,
	FLMBOOL			bOkToFindMe)
{
	RCODE					rc = NE_FLM_OK;
	FLMBOOL				bMutexLocked = FALSE;
	F_Thread *			pCurThread;

	*ppThread = NULL;

	f_mutexLock( m_hMutex);
	bMutexLocked = TRUE;

	if( m_uiNumThreads == 0)
	{
		rc = RC_SET( NE_FLM_NOT_FOUND);
		goto Exit;
	}

	pCurThread = m_pThreadList;
	while( pCurThread)
	{
		f_mutexLock( pCurThread->m_hMutex);

		if( pCurThread->m_uiThreadGroup == uiThreadGroup &&
			pCurThread->m_uiAppId == uiAppId)
		{
			if( bOkToFindMe ||
				(!bOkToFindMe && pCurThread->m_uiThreadId != f_threadId()))
			{
				// Found a match.

				pCurThread->AddRef();
				*ppThread = pCurThread;
				f_mutexUnlock( pCurThread->m_hMutex);
				goto Exit;
			}
		}

		f_mutexUnlock( pCurThread->m_hMutex);
		pCurThread = pCurThread->m_pNext;
	}

	rc = RC_SET( NE_FLM_NOT_FOUND);

Exit:

	if( bMutexLocked)
	{
		f_mutexUnlock( m_hMutex);
	}

	return( rc);
}

/****************************************************************************
Desc:		Finds a thread based on user-specified identifiers
****************************************************************************/
RCODE FLMAPI F_ThreadMgr::getNextGroupThread(
	IF_Thread **		ppThread,
	FLMUINT			uiThreadGroup,
	FLMUINT *		puiThreadId)
{
	RCODE					rc = NE_FLM_OK;
	FLMBOOL				bMutexLocked = FALSE;
	F_Thread *			pCurThread;
	F_Thread *			pFoundThread = NULL;

	f_mutexLock( m_hMutex);
	bMutexLocked = TRUE;

	if( m_uiNumThreads == 0)
	{
		rc = RC_SET( NE_FLM_NOT_FOUND);
		goto Exit;
	}

	pCurThread = m_pThreadList;
	while( pCurThread)
	{
		if( pCurThread->m_uiThreadGroup == uiThreadGroup &&
			pCurThread->m_uiThreadId > *puiThreadId)
		{
			// The threads are not kept in order by thread ID in the
			// manager's list.  So, we need to make sure we get the
			// thread with the next ID beyond the ID passed into the
			// routine.

			if( !pFoundThread ||
				pCurThread->m_uiThreadId < pFoundThread->m_uiThreadId)
			{
				pFoundThread = pCurThread;
			}
		}

		pCurThread = pCurThread->m_pNext;
	}

	if( !pFoundThread)
	{
		rc = RC_SET( NE_FLM_NOT_FOUND);
		goto Exit;
	}

	pFoundThread->AddRef();
	*ppThread = pFoundThread;
	*puiThreadId = pFoundThread->m_uiThreadId;

Exit:

	if( RC_BAD( rc))
	{
		*ppThread = NULL;
		*puiThreadId = 0xFFFFFFFF;
	}

	if( bMutexLocked)
	{
		f_mutexUnlock( m_hMutex);
	}

	return( rc);
}

/****************************************************************************
Desc:		Returns a count of the number of threads in a specified group
****************************************************************************/
FLMUINT FLMAPI F_ThreadMgr::getThreadGroupCount(
	FLMUINT			uiThreadGroup)
{
	F_Thread *		pThread;
	FLMUINT			uiCount;

	f_mutexLock( m_hMutex);

	uiCount = 0;
	pThread = m_pThreadList;
	while( pThread)
	{
		if( pThread->m_uiThreadGroup == uiThreadGroup)
		{
			uiCount++;
		}
		pThread = pThread->m_pNext;
	}

	f_mutexUnlock( m_hMutex);
	return( uiCount);
}

/****************************************************************************
Desc:
****************************************************************************/
FLMUINT FLMAPI F_ThreadMgr::allocGroupId( void)
{
	return( f_atomicInc( &m_groupCounter));
}

/****************************************************************************
Desc:    Allocate a thread object and start the thread
****************************************************************************/
RCODE FLMAPI F_ThreadMgr::createThread(
	IF_Thread **		ppThread,
	F_THREAD_FUNC		fnThread,
	const char *		pszThreadName,
	FLMUINT				uiThreadGroup,
	FLMUINT				uiAppId,
	void *				pvParm1,
	void *				pvParm2,
	FLMUINT				uiStackSize)
{
	RCODE					rc = NE_FLM_OK;
	F_Thread *			pThread = NULL;

	if( ppThread)
	{
		*ppThread = NULL;
	}

	if( (pThread = f_new F_Thread) == NULL)
	{
		rc = RC_SET( NE_FLM_MEM);
		goto Exit;
	}

	if( RC_BAD( rc = pThread->startThread(
		fnThread, pszThreadName, uiThreadGroup, uiAppId,
		pvParm1, pvParm2, uiStackSize)))
	{
		goto Exit;
	}

	if( ppThread)
	{
		*ppThread = pThread;

		// Set pThread to NULL so that the object won't be released
		// below.  The application has indicated (by passing in a
		// non-NULL ppThread) that it wants to keep a reference to
		// the thread.

		pThread = NULL;
	}

Exit:

	if( pThread)
	{
		pThread->Release();
	}

	return( rc);
}

/****************************************************************************
Desc:
****************************************************************************/
RCODE FLMAPI f_threadCreate(
	IF_Thread **			ppThread,
	F_THREAD_FUNC			fnThread,
	const char *			pszThreadName,
	FLMUINT					uiThreadGroup,
	FLMUINT					uiAppId,
	void *					pvParm1,
	void *					pvParm2,
	FLMUINT					uiStackSize)
{
	return( f_getThreadMgrPtr()->createThread( ppThread, fnThread,
		pszThreadName, uiThreadGroup, uiAppId, pvParm1, pvParm2, uiStackSize));
}

/****************************************************************************
Desc:
****************************************************************************/
void FLMAPI f_threadDestroy(
	IF_Thread **		ppThread)
{
	if( *ppThread != NULL)
	{
		(*ppThread)->stopThread();
		(*ppThread)->Release();
		*ppThread = NULL;
	}
}

/****************************************************************************
Desc:
****************************************************************************/
FLMUINT f_getpid( void)
{
#if defined( FLM_WIN)
	return _getpid();
#elif defined( FLM_UNIX)
	return getpid();
#elif defined( FLM_NLM)
	return( (FLMUINT)f_getNLMHandle());
#else
	#error "Unsupported Platform"
#endif
}

/****************************************************************************
Desc:
****************************************************************************/
FLMUINT FLMAPI f_threadId( void)
{
#ifdef FLM_WIN
	return( (FLMUINT)_threadid);
#elif defined( FLM_UNIX) || defined( FLM_LIBC_NLM)
	return( (FLMUINT)pthread_self());
#elif defined( FLM_RING_ZERO_NLM)
	return( (FLMUINT)kCurrentThread());
#else
	#error Platform not supprted
#endif
}

/****************************************************************************
Desc:
****************************************************************************/
RCODE FLMAPI FlmGetThreadInfo(
	IF_ThreadInfo **	ppThreadInfo)
{
	RCODE					rc = NE_FLM_OK;
	F_ThreadInfo *		pThreadInfo = NULL;

	if( (pThreadInfo = f_new F_ThreadInfo) == NULL)
	{
		rc = RC_SET( NE_FLM_MEM);
		goto Exit;
	}
	
	if( RC_BAD( rc = f_getThreadMgrPtr()->getThreadInfo(
								&pThreadInfo->m_pool,
								&pThreadInfo->m_pThreadInfoArray,
								&pThreadInfo->m_uiNumThreads)))
	{
		goto Exit;
	}
	
	*ppThreadInfo = pThreadInfo;
	pThreadInfo = NULL;

Exit:

	if( pThreadInfo)
	{
		pThreadInfo->Release();
	}

	return( rc);
}
