///////////////////////////////////////////////////////////////////////////////
//                                                         
// CegoAdminThreadPool.cc  
// ----------------------                                                     
// Cego admin threadpool class implementation
//                                                         
// Design and Implementation by Bjoern Lemke
//               
// (C)opyright 2000-2025 Bjoern Lemke
//
// IMPLEMENTATION MODULE
//
// Class: CegoAdminThreadPool
//
// Description: Thread pool implementation for admin message handling
//
// Status: CLEAN
//
///////////////////////////////////////////////////////////////////////////////

// CEGO INCLUDES
#include "CegoAdminThreadPool.h"
#include "CegoDefs.h"
#include "CegoAdminThread.h"

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

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

static ThreadLock queueLock("ADMQUEUE"); 
extern bool __lockStatOn;

CegoAdminThreadPool::CegoAdminThreadPool(unsigned poolLimit, CegoDatabaseManager *pDBMng, CegoDbThreadPool *pDbPool) : Thread()
{
    _poolLimit = poolLimit;
    _samplePos = 0;
    _pNet = 0;
    _pDBMng = pDBMng;
    _pDbPool = pDbPool;    
    _isAllocated=false;
    _modId = _pDBMng->getModId("CegoAdminThreadPool");
}

CegoAdminThreadPool::~CegoAdminThreadPool()  
{
    _terminated=true;
    if ( _isAllocated )
    {	
	_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 admin threads terminated"));
	    join(getTid());
	}
	else
	{
	    _pDBMng->log(_modId, Logger::NOTICE, Chain("Canceling hanging admin sessions ..."));
	    cancel();
    }
	
	unsigned i=0;
	while ( i < _poolLimit )
	{
	    delete _threadList[i];
	    i++;
	}
	
	delete _threadLoad;
	for ( unsigned i = 0; i < THRMNG_NUMLOADSAMPLE; i++ )
	{
	    delete _threadIdle[i];
	}
	
	if ( _pNet )
	    delete _pNet;
	
	delete _threadId;
	delete _numRequest;
	delete _threadState;
    }
}

bool CegoAdminThreadPool::init()
{
    try
    {

	queueLock.init(LCKMNG_LOCKWAITDELAY, __lockStatOn);
	
	if ( _pNet )
	    delete _pNet;
	
	_pNet = new Net( NETMNG_MSG_BUFLEN, NETMNG_SIZEBUFLEN, NETMNG_MAXSENDLEN );
	
#ifdef CGDEBUG
	_pDBMng->log(_modId, Logger::DEBUG, Chain("Serving admin requests on port ") + Chain(_adminPortNo));
#endif
	
	_pDBMng->getDBHost(_adminHostName);
	_pDBMng->getAdminPort(_adminPortNo);

	// throw Exception(EXLOC, "bind failure simulation");
	
	// cout << "Serving portno " << _adminPortNo << endl;
	if ( _adminHostName != Chain() )
	    _pNet->serve(_adminHostName, _adminPortNo);
	else
	    _pNet->serve6(_adminPortNo);

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

void CegoAdminThreadPool::allocate()
{
    _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));
    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 = (CegoAdminThread**)malloc(_poolLimit * sizeof(CegoAdminThread*));

    _terminated=false;

    unsigned i=0;
    while ( i < _poolLimit )
    {	
	_threadState[i] = READY;
	_threadList[i]= new CegoAdminThread(this, _pDBMng, _pDbPool);
	_threadId[i]=i;
	_numRequest[i]=0;
	_threadLoad[i]=0;
	for ( unsigned j = 0; j < THRMNG_NUMLOADSAMPLE; j++ )
	{
	    _threadIdle[j][i]=0;
	}
	
	_threadList[i]->start(&_threadId[i]);
	
	i++;
    }

    _isAllocated=true;
}



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

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

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

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

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

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


void* CegoAdminThreadPool::job(void* arg)
{
    try
    {
	if ( _pNet == 0 )
	    throw Exception(EXLOC, "Admin 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 ) 
	{	    
	    // take the mutex to avoid cpu load during wait position

	    usedTime[_samplePos] = 0;
	    
	    tim.reset();
	    tim.start();
	
	    bool noReq=false;
	    lockQueue();
	    if ( _requestQueue.Size() == 0  )  noReq=true;
	    unlockQueue();
	    
	    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 )
	    {
		unlockQueue();
		
		// in the following, we go sleep for a while
		// to give other threads a chance to aquire queue lock

		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;
	    // cout << "___" << endl;
	    // cout << "UsedTime=" << usedTime << endl;
	    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;
	    }
	}

	// to terminate active recovery session
	_pDBMng->setAllRecoveryOff();

	unsigned i=0;
	while ( i < _poolLimit )
	{
#ifdef CGDEBUG
	    _pDBMng->log(_modId, Logger::DEBUG, Chain("Waiting for admin 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("Admin Thread start failed : ") +  msg);
	_terminated=true;
    }
    return 0;
}

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

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

    NetHandler **pRequest = _requestQueue.First();

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

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

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

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

void CegoAdminThreadPool::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 CegoAdminThreadPool::verifyLocks(CegoAdminHandler*pAH)
{

    unsigned timeout = 3000;
    
    Chain info;

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

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

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

}
