windows-nt/Source/XPSP1/NT/multimedia/media/dplayx/dplay/protocol/timer.c

496 lines
10 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
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;
}