You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
11 KiB
496 lines
11 KiB
/*++
|
|
|
|
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);
|
|
hShutDownPeriodicTimer=0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
|