///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoDbThreadPool.cc  
// -------------------                                                     
// Cego db threadpool class implementation
//                                                         
// Design and Implementation by Bjoern Lemke
//               
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoDbThreadPool
// 
// Description: Managing the database threads
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// LFC INCLUDES
#include <lfcbase/Exception.h>
#include <lfcbase/NetHandler.h>
#include <lfcbase/Sleeper.h>
#include <lfcbase/ThreadLock.h>
#include <lfcbase/Sleeper.h>
#include <lfcbase/NanoTimer.h>
#include <lfcxml/XMLSuite.h>

// CEGO INCLUDES
#include "CegoXMLdef.h"
#include "CegoDbThreadPool.h"
#include "CegoDefs.h"

// POSIX INCLUDES
#include <string.h>
#include <stdlib.h>

static ThreadLock queueLock("DBQUEUE"); 

// db pool thread array is used to synchronize compiled objects use ( procedures and views ) 
static ThreadLock** thrLockArray; 
extern bool __lockStatOn;

CegoDbThreadPool::CegoDbThreadPool(unsigned poolLimit, CegoDatabaseManager *pDBMng, CegoDbHandler::ProtocolType protType)  : Thread()
{
    _poolLimit = poolLimit;
    _samplePos = 0;
    _pNet = 0;
    _pDBMng = pDBMng;
    _protType = protType;
    _isAllocated=false;
    _modId = _pDBMng->getModId("CegoDbThreadPool");
}

CegoDbThreadPool::~CegoDbThreadPool()  
{
    _terminated=true;

    if ( _isAllocated )
    {
	unsigned i=0;
	while ( i < _poolLimit )
	{
	    _threadList[i]->abortSession();	
	    i++;
	}
	
	_joined=false;
	unsigned count=0;
	while ( _joined == false && count < POOL_TERMWAIT )
	{
	    Sleeper s;
	    s.secSleep(1);
	    count++;
	}
	
	if ( _joined )
	{
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("All db threads terminated"));
	    join(getTid());
	}
	else
	{
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Canceling hanging db sessions ..."));
	    cancel();
	}
	
	for ( unsigned i = 0; i < _poolLimit; i++ )
	{
	    delete _threadList[i];
	    delete thrLockArray[i];
	}
	
	delete _numRequest;
	delete _numQueryRequest;
	delete _threadId;
	delete _threadLoad;

	for ( unsigned i = 0; i < THRMNG_NUMLOADSAMPLE; i++ )
	{
	    delete _threadIdle[i];
	}
	
	if ( _pNet )
	    delete _pNet;
	
	delete _threadState;
	delete thrLockArray;
    }
}

void CegoDbThreadPool::P(unsigned i)
{
    thrLockArray[i]->writeLock(DBM_LOCKTIMEOUT);
}

void CegoDbThreadPool::V(unsigned i)
{
    thrLockArray[i]->unlock();
}

bool CegoDbThreadPool::init()
{
    try
    {

	queueLock.init(LCKMNG_LOCKWAITDELAY, __lockStatOn);
	
	if ( _pNet )
	    delete _pNet;

	_maxSendLen = _pDBMng->getMaxSendLen();
	_pDBMng->getDataPort(_dataPortNo);
	_pDBMng->getDBHost(_dataHostName);
	
	_pNet = new Net( NETMNG_MSG_BUFLEN, NETMNG_SIZEBUFLEN, _maxSendLen );

	// throw Exception(EXLOC, "bind failure simulation");
	
	if ( _dataHostName != Chain() )
	    _pNet->serve(_dataHostName, _dataPortNo);
	else
	    _pNet->serve6(_dataPortNo);

	allocate();
	
	return true;
	
    }
    catch ( Exception e)
    {
	Chain msg;
	e.pop(msg);
	_pDBMng->log(_modId, Logger::LOGERR, Chain("Db thread pool init failed : ") +  msg);
	_terminated=true;
	
    }
    return false;    

}

void  CegoDbThreadPool::allocate()
{

    thrLockArray = new ThreadLock*[_poolLimit];

    for (unsigned i = 0; i<_poolLimit; i++)
    {
        thrLockArray[i] = new ThreadLock(Chain("DBTHRLCK") + Chain(i));
        thrLockArray[i]->init(LCKMNG_LOCKWAITDELAY, __lockStatOn);
    }
        
    _threadId = (unsigned long long*)malloc(_poolLimit * sizeof(unsigned long long));
    _threadLoad = (unsigned long long*)malloc(_poolLimit * sizeof(unsigned long long));
    _numRequest = (unsigned long long*)malloc(_poolLimit * sizeof(unsigned long long));
    _numQueryRequest = (unsigned long long*)malloc(_poolLimit * sizeof(unsigned long long));
    for ( unsigned i = 0; i < THRMNG_NUMLOADSAMPLE; i++ )
    {
	_threadIdle[i] = (unsigned long long*)malloc(_poolLimit * sizeof(unsigned long long));
    }
    _threadState = (ThreadState*)malloc(_poolLimit * sizeof(ThreadState));
    _threadList = (CegoDbThread**)malloc(_poolLimit * sizeof(CegoDbThread*));

    _terminated=false;

    unsigned i=0;
    while ( i < _poolLimit )
    {
	_threadState[i] = STARTING;
	_threadList[i]= new CegoDbThread(this, _pDBMng, _protType);
	_numRequest[i]=0;
	_numQueryRequest[i]=0;

	_threadId[i]=i;
	_threadLoad[i]=0;
	for ( unsigned j = 0; j < THRMNG_NUMLOADSAMPLE; j++ )
	{
	    _threadIdle[j][i]=0;
	}
	_threadList[i]->start(&_threadId[i]);	
	i++;
    }
    
    unsigned numReady=0;
    while ( numReady < _poolLimit )
    {	
	numReady=0;
	unsigned i=0;
	while ( i < _poolLimit )
	{
	    if ( _threadState[i] == READY )
		numReady++;
	    i++;
	}

	Sleeper s;
	s.milliSleep(1);
    }
    
    _isAllocated=true;

    _pDBMng->log(_modId, Logger::NOTICE, Chain("All db threads ready"));
}

void  CegoDbThreadPool::loadObjects(unsigned tabSetId)
{
    unsigned i=0;
    while ( i < _poolLimit )
    {
	_threadList[i]->addReloadRequest(tabSetId);
	i++;
    }
}

void  CegoDbThreadPool::unloadObjects(unsigned tabSetId)
{
    unsigned i=0;
    while ( i < _poolLimit )
    {
	_threadList[i]->unloadObjects(tabSetId);
	i++;
    }
}

void CegoDbThreadPool::invalidateObject(unsigned tabSetId, const Chain& objName, CegoObject::ObjectType type)
{
    unsigned i=0;
    while ( i < _poolLimit )
    {	
	try
	{
	    _threadList[i]->invalidateObject(tabSetId, objName, type);
	}
	catch ( Exception e )
	{
	    Chain msg;
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Ignoring error : ") + msg);
	}
	i++;
    }    
}

unsigned CegoDbThreadPool::getPoolLimit() const
{
    return _poolLimit;
}

void CegoDbThreadPool::getThreadInfo(unsigned i, unsigned long long& numRequest, unsigned long long& numQueryRequest, unsigned long long& threadLoad, unsigned long long& numAllocated, CegoDbThreadPool::ThreadState& state, Chain& lastAction)
{
    state = _threadState[i];
    numRequest = _numRequest[i];
    numQueryRequest = _numQueryRequest[i];
    threadLoad = _threadLoad[i];
    numAllocated = _threadList[i]->allocatedSortArea();
    lastAction = _threadList[i]->lastAction();
}

void CegoDbThreadPool::getThreadHistory(unsigned i, ListT<CegoDbThread::QueryEntry>& queryList)
{
    if ( i < _poolLimit )
	queryList = _threadList[i]->getLastQuery();
}

void CegoDbThreadPool::getQueryHistoryLast(unsigned numLast, ListT<CegoDbThread::QueryEntry>& queryList)
{    
    TreeT<CegoDbThread::QueryEntry> queryTree;
    
    unsigned i=0;
    while ( i < _poolLimit )
    {	
	ListT<CegoDbThread::QueryEntry> threadQueryList;
	threadQueryList = _threadList[i]->getLastQuery();
	
	CegoDbThread::QueryEntry *pEntry =  threadQueryList.First();
	while ( pEntry )
	{
	    pEntry->setRevLastOrder();
	    queryTree.Insert(*pEntry);	    
	    pEntry =  threadQueryList.Next();
	}
	
	i++;
    }

    i=0;
    CegoDbThread::QueryEntry *pEntry =  queryTree.First();
    while ( pEntry && i < numLast )
    {
	queryList.Insert(*pEntry);
	pEntry =  queryTree.Next();
	i++;
    }
}

void CegoDbThreadPool::getQueryHistoryCost(unsigned numLast, ListT<CegoDbThread::QueryEntry>& queryList)
{
    TreeT<CegoDbThread::QueryEntry> queryTree;
    
    unsigned i=0;
    while ( i < _poolLimit )
    {
	ListT<CegoDbThread::QueryEntry> threadQueryList;
	threadQueryList = _threadList[i]->getCostQuery();

	CegoDbThread::QueryEntry *pEntry =  threadQueryList.First();
	while ( pEntry )
	{
	    pEntry->setRevCostOrder();
	    queryTree.Insert(*pEntry);	    
	    pEntry =  threadQueryList.Next();
	}	
	i++;
    }

    i=0;
    CegoDbThread::QueryEntry *pEntry =  queryTree.First();
    while ( pEntry && i < numLast )
    {
	queryList.Insert(*pEntry);
	pEntry =  queryTree.Next();
	i++;
    }
}

void CegoDbThreadPool::setState(unsigned i, CegoDbThreadPool::ThreadState state)
{
    _threadState[i] = state;
}

void CegoDbThreadPool::incNumRequest(unsigned i)
{
    _numRequest[i]++;
}

void CegoDbThreadPool::incNumQueryRequest(unsigned i)
{
    _numQueryRequest[i]++;
}

void CegoDbThreadPool::addThreadIdle(unsigned i, unsigned long long usec)
{    
    _threadIdle[_samplePos][i] += usec;
}

void CegoDbThreadPool::setTid(unsigned i, unsigned long long tid)
{
    _threadId[i] = tid;
}

void CegoDbThreadPool::setThreadState(unsigned i, CegoDbThreadPool::ThreadState state)
{
    _threadState[i] = state;
}

void  CegoDbThreadPool::abortThread(unsigned i)
{
    _threadList[i]->abortSession();
}


void* CegoDbThreadPool::job(void* arg)
{
    try
    {
	if ( _pNet == 0 )
	    throw Exception(EXLOC, "Db threadpool not initialized");
		
	NanoTimer tim;
	

	unsigned long long usedTime[THRMNG_NUMLOADSAMPLE];
	for ( unsigned i = 0; i < THRMNG_NUMLOADSAMPLE; i++ )
	{
	    usedTime[i] = 0;
	}

	unsigned selectTimeout = _pDBMng->getSelectTimeout();
	unsigned queueDelay = _pDBMng->getQueueDelay();
	
	while ( ! _terminated ) 
	{
	    usedTime[_samplePos] = 0;
	    
	    tim.reset();
	    tim.start();

	    // take the mutex to avoid cpu load during wait position
	
	    bool noReq=false;
	    lockQueue();
	    if ( _requestQueue.Size() == 0  )  noReq=true;
	    unlockQueue();

	    /*
	      locking the queue to avoid cpu load for idle db threads
	      This leads to fuzzy db thread load statistics.
	      Instead we reduce cpu load with the QUEUEDELAY parameter

	      if ( noReq )
	      lockQueue();
	    */
	    
	    NetHandler* pHandle = 0;
	    
	    try
	    {		
		pHandle = _pNet->nextRequest( selectTimeout );
	    }
	    catch ( Exception e )
	    {
		Chain msg;
		e.pop(msg);
		_pDBMng->log(_modId, Logger::LOGERR, Chain("Error on incoming connection : ") + msg);
	    }
	    
	    if ( noReq )
	    {
		Sleeper s;
		s.microSleep(queueDelay);
	    }
	    
	    if ( pHandle )
	    {
		_pDBMng->log(_modId, Logger::NOTICE, Chain("Connection request from <") + pHandle->getSource() + Chain(">"));

		if ( pHandle->getMsgSize() > 0 )
		{
		    lockQueue();
		    
		    if ( _requestQueue.Size() < NETMNG_MAXQUEUELEN )
		    {
			_requestQueue.Insert(pHandle);
		    }
		    else
		    {
			delete pHandle;
			_pDBMng->log(_modId, Logger::NOTICE, Chain("Rejected incoming request since connection queue is full ( ") +  Chain(NETMNG_MAXQUEUELEN) + Chain(" max )"));
		    }

		    unlockQueue();		    
		}
		else
		{
		    delete pHandle;
		    _pDBMng->log(_modId, Logger::NOTICE, Chain("Rejected empty request message"));
		}
	    }

	    tim.stop();

	    usedTime[_samplePos] += tim.getSum();
	    
	    tim.reset();
	    tim.start();
	   
	    // calculate load
	    unsigned i=0;
	    while ( i < _poolLimit )
	    {
		unsigned long long ut=0;
		unsigned long long ti=0;
		for ( unsigned j=0; j< THRMNG_NUMLOADSAMPLE; j++ )
		{
		    ut+=usedTime[j];
		    ti+=_threadIdle[j][i];
		}
		
		unsigned long long l = 0;
		if ( ut > 0 && ut > ti )
		    l = (unsigned long long)100 - ( (unsigned long long)100 * ti ) / ut;
				
		_threadLoad[i] = l > 0 ? l : 0; 

		i++;
	    }

	    _samplePos = ( _samplePos + 1 ) % THRMNG_NUMLOADSAMPLE;
	   
	    for ( unsigned i = 0; i < _poolLimit; i++ )
	    {
		_threadIdle[_samplePos][i]=0;
	    }	    
	}
	unsigned i=0;
	while ( i < _poolLimit )
	{
#ifdef CGDEBUG	
	    _pDBMng->log(_modId, Logger::DEBUG, Chain("Waiting for db thread with tid ") + Chain(_threadList[i]->getTid()) + Chain(" to terminate"));
#endif
	    join(_threadList[i]->getTid());
	    i++;
	}
	_joined=true;
    }
    catch ( Exception e)
    {
	Chain msg;
	e.pop(msg);	
	_pDBMng->log(_modId, Logger::LOGERR, Chain("Db Thread failed : ") +  msg);	
	_terminated=true;
    }

    // waiting for db threads to terminate    
    return 0;
}

bool CegoDbThreadPool::isBusy()
{
    unsigned i=0;
    while ( i < _poolLimit )
    {
	if ( _threadState[i] == BUSY )
	    return true;
	i++;
    }
    return false;
}

bool CegoDbThreadPool::isTerminated()
{
    return _terminated;
}

NetHandler* CegoDbThreadPool::nextRequest()
{
    lockQueue();

    NetHandler **pRequest = _requestQueue.First();

    if ( pRequest )
    {
	NetHandler *pN = *pRequest;
	_requestQueue.Remove(*pRequest);
	unlockQueue();

	return pN; 
    }
    else
    {
	unlockQueue();
	return 0;
    }    
}

void CegoDbThreadPool::lockQueue()
{
    queueLock.writeLock();
}

void CegoDbThreadPool::unlockQueue()
{
    queueLock.unlock();
}

void CegoDbThreadPool::getLockStat(Chain& lockName, unsigned& lockCount, unsigned long long &numRdLock, unsigned long long &numWrLock, unsigned long long &sumRdDelay, unsigned long long &sumWrDelay)
{
    lockName = queueLock.getId();
    lockCount = queueLock.numLockTry();

    numRdLock = queueLock.numReadLock();
    numWrLock = queueLock.numWriteLock();

    sumRdDelay = 0;
    sumWrDelay = 0;

    if ( queueLock.numReadLock() > 0 )
	sumRdDelay = queueLock.sumReadDelay() / LCKMNG_DELRES;
    if ( queueLock.numWriteLock() > 0 )
	sumWrDelay = queueLock.sumWriteDelay() / LCKMNG_DELRES;
}

void CegoDbThreadPool::getAggThreadLockStat(Chain& lockGroup, unsigned& numLock, unsigned& lockCount, unsigned long long &numRdLock, unsigned long long &numWrLock, unsigned long long &sumRdDelay, unsigned long long &sumWrDelay)
{
    lockGroup = Chain("DBTHRLCK");
    
    lockCount = 0;
    numRdLock = 0;
    numWrLock = 0;
    sumRdDelay = 0;
    sumWrDelay = 0;
    numLock = 0;

    for ( unsigned i = 0; i<_poolLimit; i++)
    {	
	numLock++;
	lockCount += thrLockArray[i]->numLockTry();
	numRdLock += thrLockArray[i]->numReadLock();
	numWrLock += thrLockArray[i]->numWriteLock();
	    
	if ( thrLockArray[i]->numReadLock() > 0 )
	    sumRdDelay += thrLockArray[i]->sumReadDelay() / LCKMNG_DELRES;   
	if ( thrLockArray[i]->numWriteLock() > 0 )
	    sumWrDelay += thrLockArray[i]->sumWriteDelay() / LCKMNG_DELRES;
    }
}

void CegoDbThreadPool::verifyLocks(CegoAdminHandler* pAH)
{
    // verify queueLock
    // verify thrLockArray

    unsigned timeout = 3000;
    
    Chain info;

    info = Chain("DbThreadPool Queue - Read ...");
    if ( pAH )
	pAH->sendInfo(info);
	
    queueLock.readLock(timeout);
    queueLock.unlock();

    if ( pAH )
	pAH->sendInfo(Chain("ok\n"));

    info = Chain("DbThreadPool Queue - Write ...");
    if ( pAH )
	pAH->sendInfo(info);
    
    queueLock.writeLock(timeout);
    queueLock.unlock();
    
    if ( pAH )
	pAH->sendInfo(Chain("ok\n"));

    for ( unsigned i = 0; i<_poolLimit; i++)
    {		
	Chain info = Chain("DbThreadPool Thread - ") + Chain(i) + Chain(" Read ...");
	if ( i < _poolLimit - 1 )
	    info += Chain("\r");
	
	if ( pAH )
	    pAH->sendInfo(info);
	
	thrLockArray[i]->readLock(timeout);
	thrLockArray[i]->unlock();	
    }
	
    if ( pAH )
	pAH->sendInfo(Chain("ok\n"));

    
    for ( unsigned i = 0; i<_poolLimit; i++)
    {		
	Chain info = Chain("DbThreadPool Thread - ") + Chain(i) + Chain(" Write ...");
	if ( i < _poolLimit - 1 )
	    info += Chain("\r");
	
	if ( pAH )
	    pAH->sendInfo(info);
	
	thrLockArray[i]->writeLock(timeout);
	thrLockArray[i]->unlock();	
    }
	
    if ( pAH )
	pAH->sendInfo(Chain("ok\n"));    
}
