/*++

Copyright (c) 1996,1997  Microsoft Corporation

Module Name:

    TIMER.C

Abstract:

	Handle adjusting timer resolution for throttling and do thread pool

Author:

	Aaron Ogus (aarono)

Environment:

	Win32

Revision History:

	Date   Author  Description
   ======  ======  ============================================================
   6/04/98 aarono  Original

--*/

#include <windows.h>
#include "newdpf.h"
#include <mmsystem.h>
#include <dplay.h>
#include <dplaysp.h>
#include <dplaypr.h>
#include "mydebug.h"
#include "arpd.h"
#include "arpdint.h"
#include "macros.h"
#include "mytimer.h"


#define DEFAULT_TIME_RESOLUTION 20	/* ms */
#define MIN_TIMER_THREADS	1
#define MAX_TIMER_THREADS 5


VOID QueueTimeout(PMYTIMER pTimer);
DWORD WINAPI TimerWorkerThread(LPVOID foo);


// Timer Resolution adjustments;
DWORD dwOldPeriod=DEFAULT_TIME_RESOLUTION; 
DWORD dwCurrentPeriod=DEFAULT_TIME_RESOLUTION;
DWORD dwPeriodInUse=DEFAULT_TIME_RESOLUTION;


BILINK MyTimerList={&MyTimerList, &MyTimerList};
CRITICAL_SECTION MyTimerListLock;

LPFPOOL pTimerPool=NULL;
DWORD uWorkaroundTimerID;

DWORD twInitCount=0;	//number of times init called, only inits on 0->1, deinit on 1->0

DWORD Unique=0;


CRITICAL_SECTION ThreadListLock;		// locks ALL this stuff.

BILINK ThreadList={&ThreadList,&ThreadList};	// ThreadPool grabs work from here.

DWORD nThreads=0;		// number of running threads.
DWORD ActiveReq=0;		// number of requests being processed.
DWORD PeakReqs=0;
DWORD bShutDown=FALSE;
DWORD bAlreadyCleanedUp=FALSE;
DWORD KillCount=0;
DWORD ExtraSignals=0;

HANDLE hWorkToDoSem;
HANDLE hShutDownPeriodicTimer;

DWORD_PTR uAdjustResTimer=0;
DWORD AdjustResUnique=0;

DWORD_PTR uAdjustThreadsTimer=0;
DWORD AdjustThreadsUnique=0;

// Sometimes scheduled retry timers don't run.  This runs every 10 seconds to catch
// timers that should have been expired.
void CALLBACK PeriodicTimer (UINT uID, UINT uMsg, DWORD_PTR dwUser, DWORD_PTR dw1, DWORD_PTR dw2)
{
	DWORD  time;
	PMYTIMER  pTimerWalker;
	BILINK *pBilink;
	DWORD dwReleaseCount=0;
	DWORD slowcount=0;

	if(bShutDown){
		if(!InterlockedExchange(&bAlreadyCleanedUp,1)){
			while(nThreads && slowcount < (60000/50)){	// don't wait more than 60 seconds.
				slowcount++;
				Sleep(50);
			}
			if(!nThreads){ // better to leak than to crash.
				DeleteCriticalSection(&MyTimerListLock);
				DeleteCriticalSection(&ThreadListLock);
			}	
			timeKillEvent(uID);
			ASSERT(hShutDownPeriodicTimer);
			SetEvent(hShutDownPeriodicTimer);
		}	
		return;
	}

	time=timeGetTime()+(dwCurrentPeriod/2);
		
	Lock(&MyTimerListLock);
	Lock(&ThreadListLock);

	pBilink=MyTimerList.next;

	while(pBilink!=&MyTimerList){
	
		pTimerWalker=CONTAINING_RECORD(pBilink, MYTIMER, Bilink);
		pBilink=pBilink->next;

		if(((INT)(time-pTimerWalker->TimeOut) > 0)){
			Delete(&pTimerWalker->Bilink);
			InsertBefore(&pTimerWalker->Bilink, &ThreadList);
			pTimerWalker->TimerState=QueuedForThread;
			dwReleaseCount++;
		} else {
			break;
		}

	}

	ActiveReq += dwReleaseCount;
	if(ActiveReq > PeakReqs){
		PeakReqs=ActiveReq;
	}
	
	ReleaseSemaphore(hWorkToDoSem,dwReleaseCount,NULL);

	Unlock(&ThreadListLock);
	Unlock(&MyTimerListLock);

}

#define min(a,b)            (((a) < (b)) ? (a) : (b))

VOID CALLBACK AdjustTimerResolution(UINT_PTR uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2)
{
	DWORD dwWantPeriod;

	dwWantPeriod=min(dwCurrentPeriod,dwOldPeriod);
	dwOldPeriod=dwCurrentPeriod;
	dwCurrentPeriod=DEFAULT_TIME_RESOLUTION;
	
	if(dwPeriodInUse != dwWantPeriod){
		dwPeriodInUse=dwWantPeriod;
		timeKillEvent(uWorkaroundTimerID);
		uWorkaroundTimerID=timeSetEvent(dwPeriodInUse, dwPeriodInUse, PeriodicTimer, 0, TIME_PERIODIC); 
	}
	uAdjustResTimer=SetMyTimer(1000,500,AdjustTimerResolution,0,&AdjustResUnique);
}

VOID CALLBACK AdjustThreads(UINT_PTR uID, UINT uMsg, DWORD_PTR dwUser, DWORD dw1, DWORD dw2)
{
	Lock(&ThreadListLock);
	if((PeakReqs < nThreads) && nThreads){
		KillCount=nThreads-PeakReqs;
		ReleaseSemaphore(hWorkToDoSem, KillCount, NULL);
	}
	PeakReqs=0;
	Unlock(&ThreadListLock);
	
	uAdjustThreadsTimer=SetMyTimer(60000,500,AdjustThreads,0,&AdjustThreadsUnique);
}

VOID SetTimerResolution(UINT msResolution)
{

	if(!msResolution || msResolution >= 20){
		return;
	}

	if(msResolution < dwCurrentPeriod){
		dwCurrentPeriod=msResolution;
	}
	
}

DWORD_PTR SetMyTimer(DWORD dwTimeOut, DWORD TimerRes, MYTIMERCALLBACK TimerCallBack, DWORD_PTR UserContext, PUINT pUnique)
{

	BILINK *pBilink;
	PMYTIMER pMyTimerWalker,pTimer;
	DWORD time;
	BOOL bInserted=FALSE;

	pTimer=pTimerPool->Get(pTimerPool);
	
	if(!pTimer){
		*pUnique=0;
		return 0;
	}

	pTimer->CallBack=TimerCallBack;
	pTimer->Context=UserContext;

	SetTimerResolution(TimerRes);
	
	Lock(&MyTimerListLock);
	
		++Unique;
		if(Unique==0){
			++Unique;
		}
		*pUnique=Unique;

		pTimer->Unique=Unique;
	
		time=timeGetTime();
		pTimer->TimeOut=time+dwTimeOut;
		pTimer->TimerState=WaitingForTimeout;
	

		// Insert this guy in the list by timeout time.
		pBilink=MyTimerList.prev;
		while(pBilink != &MyTimerList){
			pMyTimerWalker=CONTAINING_RECORD(pBilink, MYTIMER, Bilink);
			pBilink=pBilink->prev;
			
			if((int)(pTimer->TimeOut-pMyTimerWalker->TimeOut) > 0 ){
				InsertAfter(&pTimer->Bilink, &pMyTimerWalker->Bilink);
				bInserted=TRUE;
				break;
			}
		}

		if(!bInserted){
			InsertAfter(&pTimer->Bilink, &MyTimerList);
		}
	
	Unlock(&MyTimerListLock);

	return (DWORD_PTR)pTimer;
}


HRESULT CancelMyTimer(DWORD_PTR dwTimer, DWORD Unique)
{
	PMYTIMER pTimer=(PMYTIMER)dwTimer;
	HRESULT hr=DPERR_GENERIC;
	
	Lock(&MyTimerListLock);
	Lock(&ThreadListLock);

	if(pTimer->Unique == Unique){
		switch(pTimer->TimerState){
			case WaitingForTimeout:
				Delete(&pTimer->Bilink);
				pTimer->TimerState=End;
				pTimer->Unique=0;
				pTimerPool->Release(pTimerPool, pTimer);
				hr=DP_OK;
				break;

			case QueuedForThread:
				Delete(&pTimer->Bilink);
				pTimer->TimerState=End;
				pTimer->Unique=0;
				pTimerPool->Release(pTimerPool, pTimer);
				if(ActiveReq)ActiveReq--;
				ExtraSignals++;
				hr=DP_OK;
				break;

			default:
				break;
		}
	}

	Unlock(&ThreadListLock);
	Unlock(&MyTimerListLock);
	return hr;
}

HRESULT InitTimerWorkaround()
{
	DWORD dwJunk;
	HANDLE hWorker=NULL;

	if(twInitCount++){//DPLAY LOCK HELD DURING CALL
		return DP_OK;
	}
	
    pTimerPool=NULL;
    nThreads=0;		// number of running threads.
    ActiveReq=0;		// number of requests being processed.
    PeakReqs=0;
    bShutDown=FALSE;
    KillCount=0;
	ExtraSignals=0;
	bAlreadyCleanedUp=FALSE;
	hWorkToDoSem=0;
	hShutDownPeriodicTimer=0;
	uAdjustResTimer=0;
	uAdjustThreadsTimer=0;
	uWorkaroundTimerID=0;

	hWorkToDoSem=CreateSemaphoreA(NULL,0,65535,NULL);
	hShutDownPeriodicTimer=CreateEventA(NULL,FALSE,FALSE,NULL);

	InitializeCriticalSection(&MyTimerListLock);
	InitializeCriticalSection(&ThreadListLock);

	pTimerPool=FPM_Init(sizeof(MYTIMER),NULL,NULL,NULL);
	
	
	if(!hWorkToDoSem || !pTimerPool || !hShutDownPeriodicTimer){
		FiniTimerWorkaround();
		return DPERR_OUTOFMEMORY;
	}

	uWorkaroundTimerID=timeSetEvent(DEFAULT_TIME_RESOLUTION, DEFAULT_TIME_RESOLUTION, PeriodicTimer, 0, TIME_PERIODIC); 

	if(!uWorkaroundTimerID){
		FiniTimerWorkaround();
		return DPERR_OUTOFMEMORY;
	}

	nThreads=1;
	hWorker=CreateThread(NULL,4096, TimerWorkerThread, NULL, 0, &dwJunk);
	if(!hWorker){
		nThreads=0;
		FiniTimerWorkaround();
		return DPERR_OUTOFMEMORY;
	}
	CloseHandle(hWorker);

	uAdjustResTimer=SetMyTimer(1000,500,AdjustTimerResolution,0,&AdjustResUnique);
	uAdjustThreadsTimer=SetMyTimer(60000,500,AdjustThreads,0,&AdjustThreadsUnique);
	
	
	return DP_OK;

}

VOID FiniTimerWorkaround()
{
	UINT slowcount=0;
	BILINK *pBilink;
	PMYTIMER pTimer;

	if(--twInitCount){ //DPLAY LOCK HELD DURING CALL
		return;
	}

	if(uAdjustResTimer){
		CancelMyTimer(uAdjustResTimer, AdjustResUnique);
	}
	if(uAdjustThreadsTimer){
		CancelMyTimer(uAdjustThreadsTimer, AdjustThreadsUnique);
	}	
	//ASSERT_EMPTY_BILINK(&MyTimerList);
	//ASSERT_EMPTY_BILINK(&ThreadList);
	bShutDown=TRUE;
	ReleaseSemaphore(hWorkToDoSem,10000,NULL);
	while(nThreads && slowcount < (60000/50)){	// don't wait more than 60 seconds.
		slowcount++;
		Sleep(50);
	}
	
	if(uWorkaroundTimerID){
		if(hShutDownPeriodicTimer){
			WaitForSingleObject(hShutDownPeriodicTimer,INFINITE);
		}	
	} else {
		DeleteCriticalSection(&MyTimerListLock);
		DeleteCriticalSection(&ThreadListLock);
	}	

	if(hShutDownPeriodicTimer){
		CloseHandle(hShutDownPeriodicTimer);
	}

	CloseHandle(hWorkToDoSem);

	while(!EMPTY_BILINK(&MyTimerList)){
		pBilink=MyTimerList.next;
		pTimer=CONTAINING_RECORD(pBilink, MYTIMER, Bilink);
		pTimer->Unique=0;
		pTimer->TimerState=End;
		Delete(&pTimer->Bilink);
		pTimerPool->Release(pTimerPool, pTimer);
	}

	while(!EMPTY_BILINK(&MyTimerList)){
		pBilink=ThreadList.next;
		pTimer=CONTAINING_RECORD(pBilink, MYTIMER, Bilink);
		pTimer->Unique=0;
		pTimer->TimerState=End;
		Delete(&pTimer->Bilink);
		pTimerPool->Release(pTimerPool, pTimer);
	}
	
	if(pTimerPool){
		pTimerPool->Fini(pTimerPool,FALSE);
		pTimerPool=NULL;
	}
}


DWORD WINAPI TimerWorkerThread(LPVOID foo)
{
	BILINK *pBilink;
	PMYTIMER pTimer;
	HANDLE hNewThread;
	DWORD dwJunk;
	
	while (1){
	
		WaitForSingleObject(hWorkToDoSem, INFINITE);

		Lock(&ThreadListLock);

			if(bShutDown || (KillCount && nThreads > 1)){
				nThreads--;
				if(KillCount && !bShutDown){
					KillCount--;
				}	
				Unlock(&ThreadListLock);
				break;	
			}

			if(ExtraSignals){
				ExtraSignals--;
				Unlock(&ThreadListLock);
				continue;
			}

			if(KillCount){
				KillCount--;
				Unlock(&ThreadListLock);
				continue;
			}

			if(ActiveReq > nThreads && nThreads < MAX_TIMER_THREADS){
				nThreads++;
				hNewThread=CreateThread(NULL,4096, TimerWorkerThread, NULL, 0, &dwJunk);
				if(hNewThread){
					CloseHandle(hNewThread);
				} else {
					nThreads--;
				}
			}

			
			pBilink=ThreadList.next;

			if(pBilink == &ThreadList) {
				Unlock(&ThreadListLock);
				continue;
			};
			
			Delete(pBilink);	// pull off the list.
			
			pTimer=CONTAINING_RECORD(pBilink, MYTIMER, Bilink);

			// Call a callback

			pTimer->TimerState=InCallBack;
		
		Unlock(&ThreadListLock);
		
		(pTimer->CallBack)((UINT_PTR)pTimer, 0, pTimer->Context, 0, 0);

		pTimer->Unique=0;
		pTimer->TimerState=End;
		pTimerPool->Release(pTimerPool, pTimer);

		Lock(&ThreadListLock);
			
		if(ActiveReq)ActiveReq--;

		Unlock(&ThreadListLock);
	}	
	return 0;
}



