Leaked source code of windows server 2003
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.
 
 
 
 
 
 

939 lines
27 KiB

//+---------------------------------------------------------------------------
//
// Microsoft Windows
// Copyright (C) Microsoft Corporation, 1992 - 1993.
//
// File: runobj.cxx
//
// Contents: Run instance object class implementations.
//
// Classes: CRun and CRunList
//
// History: 14-Mar-96 EricB Created.
// 10-Nov-96 AnirudhS Fixed CRunList::AddSorted to discard the
// appropriate element if the list is at its maximum size.
// Fixed CRunList::MakeSysTimeArray to call CoTaskMemAlloc
// once instead of calling CoTaskMemRealloc in a loop.
//
//----------------------------------------------------------------------------
#include "..\pch\headers.hxx"
#pragma hdrstop
#include <job_cls.hxx>
#include <misc.hxx>
#include <debug.hxx>
#include "..\svc_core\svc_core.hxx"
#include <userenv.h> // UnloadUserProfile
PFNSetThreadExecutionState pfnSetThreadExecutionState;
DWORD g_WakeCountSlot = 0xFFFFFFFF;
//+----------------------------------------------------------------------------
//
// Run instance object class
//
//-----------------------------------------------------------------------------
//+----------------------------------------------------------------------------
//
// Method: CRun::CRun
//
// Synopsis: ctor for time-sorted lists.
//
//-----------------------------------------------------------------------------
CRun::CRun(LPFILETIME pft, LPFILETIME pftDeadline, FILETIME ftKill,
DWORD MaxRunTime, DWORD rgFlags, WORD wIdleWait,
BOOL fKeepAfterRunning) :
m_ft(*pft),
m_ftDeadline(*pftDeadline),
m_ftKill(ftKill),
m_hJob(NULL),
m_ptszJobName(NULL),
m_dwProcessId(0),
m_hUserToken(NULL),
m_ptszDesktop(NULL),
m_ptszStation(NULL),
m_hProfile(NULL),
m_rgFlags(rgFlags),
m_dwMaxRunTime(MaxRunTime),
m_wIdleWait(wIdleWait),
m_fKeepInList(fKeepAfterRunning),
m_fStarted(FALSE),
m_bCloseUserHandle(FALSE)
{
schDebugOut((DEB_TRACE, "CRun::CRun(0x%x)\n", this));
}
//+----------------------------------------------------------------------------
//
// Method: CRun::CRun
//
// Synopsis: ctor for non-time-sorted lists.
//
//-----------------------------------------------------------------------------
CRun::CRun(DWORD MaxRunTime, DWORD rgFlags, FILETIME ftDeadline,
BOOL fKeepAfterRunning) :
m_ftDeadline(ftDeadline),
m_ftKill(MAX_FILETIME),
m_hJob(NULL),
m_ptszJobName(NULL),
m_dwProcessId(0),
m_ptszDesktop(NULL),
m_ptszStation(NULL),
m_hUserToken(NULL),
m_hProfile(NULL),
m_rgFlags(rgFlags),
m_dwMaxRunTime(MaxRunTime),
m_wIdleWait(0),
m_fKeepInList(fKeepAfterRunning),
m_fStarted(FALSE),
m_bCloseUserHandle(FALSE)
{
//schDebugOut((DEB_TRACE, "CRun::CRun(0x%x)\n", this));
//
// This ctor is used for non-sorted lists. Set the time elements to
// non-zero values to distinguish these elements from the head.
//
// CODEWORK - Don't use 0,0 to mark the head. Remove IsNull() method.
// Instead use a NULL Next pointer to mark the last list element.
//
m_ft.dwLowDateTime = 1;
m_ft.dwHighDateTime = 1;
}
//+----------------------------------------------------------------------------
//
// Method: CRun::CRun
//
// Synopsis: ctor for idle-time-sorted lists.
//
//-----------------------------------------------------------------------------
CRun::CRun(DWORD MaxRunTime, DWORD rgFlags, WORD wIdleWait,
FILETIME ftDeadline, BOOL fKeepAfterRunning) :
m_ftDeadline(ftDeadline),
m_ftKill(MAX_FILETIME),
m_hJob(NULL),
m_ptszJobName(NULL),
m_dwProcessId(0),
m_ptszDesktop(NULL),
m_ptszStation(NULL),
m_hUserToken(NULL),
m_hProfile(NULL),
m_rgFlags(rgFlags),
m_dwMaxRunTime(MaxRunTime),
m_wIdleWait(wIdleWait),
m_fKeepInList(fKeepAfterRunning),
m_fStarted(FALSE),
m_bCloseUserHandle(FALSE)
{
TRACE3(CRun,CRun);
//
// Set the time elements to non-zero values to distinguish
// these elements from the head.
// CODEWORK - as above, don't do this.
//
m_ft.dwLowDateTime = 1;
m_ft.dwHighDateTime = 1;
}
//+----------------------------------------------------------------------------
//
// Method: CRun::CRun
//
// Synopsis: copy ctor.
//
// Notes: This ctor should not be used to copy running objects, i.e.
// objects that have valid process, user token, or profile
// handles.
//
//-----------------------------------------------------------------------------
CRun::CRun(CRun * pRun) :
m_ft(pRun->m_ft),
m_ftDeadline(pRun->m_ftDeadline),
m_ftKill(pRun->m_ftKill),
m_hJob(NULL),
m_ptszJobName(NULL),
m_dwProcessId(pRun->m_dwProcessId),
m_ptszDesktop(NULL),
m_ptszStation(NULL),
m_hUserToken(NULL),
m_hProfile(NULL),
m_rgFlags(pRun->m_rgFlags),
m_dwMaxRunTime(pRun->m_dwMaxRunTime),
m_wIdleWait(pRun->m_wIdleWait),
m_fKeepInList(pRun->m_fKeepInList),
m_fStarted(pRun->m_fStarted),
m_bCloseUserHandle(FALSE)
{
TRACE3(CRun,CRun(Copy));
SetName(pRun->m_ptszJobName);
schAssert(!pRun->m_hJob);
schAssert(!pRun->m_hUserToken);
schAssert(!pRun->m_hProfile);
}
//+---------------------------------------------------------------------------
//
// Method: CRun::CRun
//
// Synopsis: ctor
//
//----------------------------------------------------------------------------
CRun::CRun(void) :
m_ftDeadline(MAX_FILETIME),
m_ftKill(MAX_FILETIME),
m_hJob(NULL),
m_ptszJobName(NULL),
m_dwProcessId(0),
m_ptszDesktop(NULL),
m_ptszStation(NULL),
m_hUserToken(NULL),
m_hProfile(NULL),
m_rgFlags(0),
m_dwMaxRunTime(RUN_TIME_NO_END),
m_wIdleWait(0),
m_fKeepInList(FALSE),
m_fStarted(FALSE),
m_bCloseUserHandle(FALSE)
{
//schDebugOut((DEB_TRACE, "CRun::CRun(0x%x)\n", this));
//
// The null arg ctor is used only by CRunList for its head element
// member. The zero time value for this element marks it as the head
// when traversing the doubly linked list.
//
m_ft.dwLowDateTime = 0;
m_ft.dwHighDateTime = 0;
}
//+---------------------------------------------------------------------------
//
// Method: CRun::~CRun
//
// Synopsis: dtor
//
//----------------------------------------------------------------------------
CRun::~CRun()
{
BOOL fOk;
//schDebugOut((DEB_TRACE, "CRun::~CRun(0x%x)\n", this));
if (m_hJob)
{
CloseHandle(m_hJob);
}
if (m_ptszJobName)
{
delete m_ptszJobName;
}
if (m_hProfile)
{
fOk = UnloadUserProfile(m_hUserToken, m_hProfile);
if (!fOk)
{
ERR_OUT("~CRun: UnloadUserProfile",
HRESULT_FROM_WIN32(GetLastError()));
}
}
if (m_hUserToken && m_bCloseUserHandle)
{
fOk = CloseHandle(m_hUserToken);
if (!fOk)
{
ERR_OUT("~CRun: CloseHandle",
HRESULT_FROM_WIN32(GetLastError()));
}
}
if ( m_ptszDesktop )
{
delete m_ptszDesktop;
}
if ( m_ptszStation )
{
delete m_ptszStation;
}
}
//+---------------------------------------------------------------------------
//
// Method: CRun::SetName
//
// Synopsis: Set the job name property. This is the folder-relative name.
//
//----------------------------------------------------------------------------
HRESULT
CRun::SetName(LPCTSTR ptszName)
{
if (m_ptszJobName)
{
delete m_ptszJobName;
m_ptszJobName = NULL;
}
if (!ptszName)
{
return S_OK;
}
size_t cchBuff = lstrlen(ptszName) + 1;
m_ptszJobName = new TCHAR[cchBuff];
if (m_ptszJobName == NULL)
{
return(E_OUTOFMEMORY);
}
StringCchCopy(m_ptszJobName, cchBuff, ptszName);
return(S_OK);
}
//+---------------------------------------------------------------------------
//
// Method: CRun::SetDesktop
//
// Synopsis: Set the Desktop name property. This is the windows station \ desktop.
//
//----------------------------------------------------------------------------
HRESULT
CRun::SetDesktop( LPCTSTR ptszDesktop )
{
if (m_ptszDesktop)
{
delete m_ptszDesktop;
m_ptszDesktop = NULL;
}
if (!ptszDesktop)
{
return S_OK;
}
size_t cchBuff = lstrlen(ptszDesktop) + 1;
m_ptszDesktop = new TCHAR[cchBuff];
if (m_ptszDesktop == NULL)
{
return(E_OUTOFMEMORY);
}
StringCchCopy(m_ptszDesktop, cchBuff, ptszDesktop);
return(S_OK);
}
//+---------------------------------------------------------------------------
//
// Method: CRun::SetStation
//
// Synopsis: Set the Desktop name property. This is the windows station \ desktop.
//
//----------------------------------------------------------------------------
HRESULT
CRun::SetStation( LPCTSTR ptszStation )
{
if (m_ptszStation)
{
delete m_ptszStation;
m_ptszStation = NULL;
}
if (!ptszStation)
{
return S_OK;
}
size_t cchBuff = lstrlen(ptszStation) + 1;
m_ptszStation = new TCHAR[cchBuff];
if (m_ptszStation == NULL)
{
return(E_OUTOFMEMORY);
}
StringCchCopy(m_ptszStation, cchBuff, ptszStation);
return(S_OK);
}
//+---------------------------------------------------------------------------
//
// Method: CRun::AdjustKillTimeByMaxRunTime
//
// Synopsis: If the job has a max run time, advance its kill time to "now"
// plus the max run time.
//
//----------------------------------------------------------------------------
void
CRun::AdjustKillTimeByMaxRunTime(FILETIME ftNow)
{
if (m_dwMaxRunTime != INFINITE)
{
AdvanceKillTime(FTfrom64(
FTto64(ftNow) +
(DWORDLONG) m_dwMaxRunTime *
FILETIMES_PER_MILLISECOND));
}
}
//+----------------------------------------------------------------------------
//
// Run object list class
//
//-----------------------------------------------------------------------------
//+----------------------------------------------------------------------------
//
// Member: CRunList::FreeList
//
// Synopsis: Frees the linked list elements, skipping the head.
//
//-----------------------------------------------------------------------------
void
CRunList::FreeList(void)
{
//
// Skip the head, it is a placeholder in the circular list with a time
// value of zero. The zero time value is used as a marker so that we can
// tell when we have traversed the entire list.
//
CRun * pCur = m_RunHead.Next();
while (!pCur->IsNull())
{
CRun * pNext = pCur->Next();
pCur->UnLink();
delete pCur;
pCur = pNext;
}
}
//+----------------------------------------------------------------------------
//
// Member: CRunList::AddSorted
//
// Synopsis: Add to the list in time sorted order.
//
// Arguments: [ftRun] -
// [ftDeadline] -
// [ftKillTime] -
// [ptszJobName] -
// [dwJobFlags] -
// [dwMaxRunTime] -
// [wIdleWait] -
// [pCount] - On entry and on exit, points to the number of
// elements in the list.
// [cLimit] - Limit on the number of elements in the list.
//
// Returns: S_OK - new run added to the list.
// S_FALSE - new run not added to the list because the list has
// already reached its size limit and the new job's run time
// is later than the last run time in the list.
//
//-----------------------------------------------------------------------------
HRESULT
CTimeRunList::AddSorted(FILETIME ftRun, FILETIME ftDeadline, FILETIME ftKillTime,
LPTSTR ptszJobName,
DWORD dwJobFlags, DWORD dwMaxRunTime, WORD wIdleWait,
WORD * pCount, WORD cLimit)
{
schAssert(*pCount <= cLimit);
//
// The list is monotonically increasing in time. Traverse the list in
// reverse order since the most common case will be to put the new
// element at the end. That is, except in the case of overlapping
// duration intervals, the run times for the same trigger will be
// discovered in monotonically increasing order.
//
// For merging in the run times from separate triggers or jobs, the runs
// will not be in any predictable order. In this case, it doesn't matter
// from which end the search starts.
//
// CODEWORK Use IsNull() instead of GetTime(). Make this loop a for loop.
//
FILETIME ftCur;
CRun * pRun = m_RunHead.Prev();
pRun->GetTime(&ftCur);
//
// Note that the head element is merely a marker (since this is a doubly
// linked, circular list) and has its time value set to zero. Thus if we
// reach a zero FILETIME, we have reached the head and thus know that
// there is no list element with a later time, so insert at the tail.
//
while (ftCur.dwLowDateTime || ftCur.dwHighDateTime)
{
if (CompareFileTime(&ftCur, &ftRun) == 0)
{
//
// Duplicate found, check for job name match. If here as a result
// of a call to ITask::GetRunTimes, then both will be null. We
// want duplicates eliminated in this case. Otherwise, compare
// names.
//
if ((pRun->GetName() == NULL && ptszJobName == NULL) ||
(pRun->GetName() != NULL && ptszJobName != NULL &&
lstrcmpi(pRun->GetName(), ptszJobName) == 0))
{
// keep the one already in the list but set the kill time
// to the earlier of the two,
// set the idle wait time to the lesser (less restrictive)
// of the two
// and set the start deadline to the later (less
// restrictive) of the two.
//
pRun->ReduceWaitTo(wIdleWait);
pRun->AdvanceKillTime(ftKillTime);
pRun->RelaxDeadline(ftDeadline);
// (There is no reason for the MaxRunTime to be different)
schAssert(pRun->GetMaxRunTime() == dwMaxRunTime);
return S_OK;
}
}
if (CompareFileTime(&ftCur, &ftRun) < 0)
{
//
// The new run is later than the current, so we are at the
// insertion point.
//
break;
}
pRun = pRun->Prev();
pRun->GetTime(&ftCur);
}
//
// If the list is already at its maximum size, discard either the
// last element or the one we were about to insert, whichever is
// later.
//
if (*pCount >= cLimit)
{
CRun * pLast = m_RunHead.Prev();
if (pLast == pRun)
{
//
// We were about to insert after the last element.
//
return S_FALSE;
}
else
{
//
// Discard the last element before inserting the new one.
//
pLast->UnLink();
delete pLast;
(*pCount)--;
}
}
//
// Create the new element and insert after the current one.
//
CRun * pNewRun = new CRun(&ftRun, &ftDeadline, ftKillTime, dwMaxRunTime,
dwJobFlags, wIdleWait, FALSE);
if (!pNewRun)
{
ERR_OUT("RunList: Add", E_OUTOFMEMORY);
return E_OUTOFMEMORY;
}
HRESULT hr = pNewRun->SetName(ptszJobName);
if (FAILED(hr))
{
ERR_OUT("CRunList::AddSorted SetName", hr);
delete pNewRun;
return hr;
}
pNewRun->SetNext(pRun->Next());
pNewRun->Next()->SetPrev(pNewRun);
pRun->SetNext(pNewRun);
pNewRun->SetPrev(pRun);
//
// Increment the count.
//
(*pCount)++;
return S_OK;
}
//+----------------------------------------------------------------------------
//
// Member: CIdleRunList::AddSortedByIdleWait
//
// Synopsis: Add to the list in time sorted order.
//
//-----------------------------------------------------------------------------
void
CIdleRunList::AddSortedByIdleWait(CRun * pAdd)
{
//
// If the system needs to stay awake to run this task, increment the
// thread's wake count. (We know that this is always called by the
// worker thread.)
//
if (pAdd->IsFlagSet(TASK_FLAG_SYSTEM_REQUIRED))
{
WrapSetThreadExecutionState(TRUE, "AddSortedByIdleWait");
}
if (m_RunHead.Next()->IsNull())
{
// List is empty, so add this as the first element.
//
pAdd->LinkAfter(&m_RunHead);
return;
}
WORD wAddWait = pAdd->GetWait();
schAssert(wAddWait > 0); // We should never put a job in the idle wait
// list if its idle wait time is 0
//
// Walk the list, comparing idle wait times.
//
CRun * pCur = m_RunHead.Next();
while (!pCur->IsNull())
{
if (wAddWait < pCur->GetWait())
{
pAdd->LinkBefore(pCur);
return;
}
pCur = pCur->Next();
}
//
// Add to the end of the list.
//
pAdd->LinkBefore(pCur);
}
//+----------------------------------------------------------------------------
//
// Member: CIdleRunList::GetFirstWait
//
// Synopsis: Finds the lowest idle wait time of the jobs in the list that
// haven't already been started in this idle period.
// Returns 0xffff if there is no such job.
//
//-----------------------------------------------------------------------------
WORD
CIdleRunList::GetFirstWait()
{
for (CRun * pRun = m_RunHead.Next();
!pRun->IsNull();
pRun = pRun->Next())
{
if (!pRun->m_fStarted)
{
// (We should never have inserted a run with zero wait time)
schAssert(pRun->GetWait() != 0);
return (pRun->GetWait());
}
}
return 0xffff;
}
//+----------------------------------------------------------------------------
//
// Member: CIdleRunList::MarkNoneStarted
//
// Synopsis: Marks all jobs in the idle list as not having been started in
// the current idle period.
//
//-----------------------------------------------------------------------------
void
CIdleRunList::MarkNoneStarted()
{
schDebugOut((DEB_IDLE, "Marking idle jobs as not started\n"));
for (CRun * pRun = GetFirstJob();
!pRun->IsNull();
pRun = pRun->Next())
{
pRun->m_fStarted = FALSE;
}
}
//+----------------------------------------------------------------------------
//
// Member: CRunList::AddCopy
//
// Synopsis: Add a copy of the object to the list.
//
//-----------------------------------------------------------------------------
HRESULT
CRunList::AddCopy(CRun * pOriginal)
{
CRun * pCopy = new CRun(pOriginal);
if (pCopy == NULL)
{
return E_OUTOFMEMORY;
}
pCopy->LinkAfter(&m_RunHead);
return S_OK;
}
//+----------------------------------------------------------------------------
//
// Member: CTimeRunList::Pop
//
// Synopsis: Removes the first (earliest) time element from the list and
// returns it.
//
//-----------------------------------------------------------------------------
CRun *
CTimeRunList::Pop(void)
{
CRun * pPop = m_RunHead.Next();
if (pPop->IsNull())
{
// List is empty, so return a flag return code.
//
return NULL;
}
pPop->UnLink();
return pPop;
}
//+----------------------------------------------------------------------------
//
// Member: CTimeRunList::PeekHeadTime
//
// Synopsis: Returns the filetime value for the element at the head of the
// list.
//
//-----------------------------------------------------------------------------
HRESULT
CTimeRunList::PeekHeadTime(LPFILETIME pft)
{
if (m_RunHead.Next()->IsNull())
{
// List is empty, so return a flag return code.
//
return S_FALSE;
}
m_RunHead.Next()->GetTime(pft);
return S_OK;
}
//+----------------------------------------------------------------------------
//
// Function: CTimeRunList::MakeSysTimeArray
//
// Synopsis: returns the run list times as an array of SYSTEMTIME structs.
//
// Arguments: [prgst] - a pointer to the returned array of filetime structs
// is stored here. This function allocates the array
// using CoTaskMemAlloc. It must be freed by the caller.
// [pCount] - On entry, points to an upper limit on the number of
// array elements to return. On exit, points to the
// actual number returned.
//
// Returns: E_OUTOFMEMORY, S_OK
//
//-----------------------------------------------------------------------------
HRESULT
CTimeRunList::MakeSysTimeArray(LPSYSTEMTIME * prgst, WORD * pCount)
{
WORD cLimit = *pCount;
*pCount = 0;
*prgst = (LPSYSTEMTIME) CoTaskMemAlloc(cLimit * sizeof(SYSTEMTIME));
if (*prgst == NULL)
{
return E_OUTOFMEMORY;
}
//
// Skip the head, it is a placeholder in the circular list with a time
// value of zero.
//
for (CRun * pCur = m_RunHead.Next();
(*pCount < cLimit) && (!pCur->IsNull());
(*pCount)++, pCur = pCur->Next())
{
pCur->GetSysTime( &(*prgst)[*pCount] );
}
return S_OK;
}
//+----------------------------------------------------------------------------
//
// Member: CIdleRunList::FreeList
//
// Synopsis: Same as CRunList::FreeList except it decrements the thread's
// wake count for each system-required run in the list. (We
// know this method is only called by the worker thread.)
//
//-----------------------------------------------------------------------------
void
CIdleRunList::FreeList()
{
CRun * pCur = m_RunHead.Next();
while (!pCur->IsNull())
{
CRun * pNext = pCur->Next();
pCur->UnLink();
if (pCur->IsFlagSet(TASK_FLAG_SYSTEM_REQUIRED))
{
WrapSetThreadExecutionState(FALSE, "CIdleRunList::FreeList");
}
delete pCur;
pCur = pNext;
}
}
//+----------------------------------------------------------------------------
//
// Member: CIdleRunList::FreeExpiredOrRegenerated
//
// Synopsis: This method is called when rebuilding the idle wait list from
// the data in the tasks folder.
// Removes runs that have m_fKeepInList set. (These correspond
// to jobs with idle triggers.) Also purges expired runs.
// Runs that don't have m_fKeepInList set correspond to runs that
// have been triggered due to some other event, and are waiting
// for an idle period; these are not removed here.
//
//-----------------------------------------------------------------------------
void
CIdleRunList::FreeExpiredOrRegenerated()
{
// BUGBUG ftNow should be a parameter
FILETIME ftNow = GetLocalTimeAsFileTime();
CRun * pNext;
for (CRun *pRun = m_RunHead.Next();
!pRun->IsNull();
pRun = pNext)
{
pNext = pRun->Next();
if (pRun->IsIdleTriggered() ||
CompareFileTime(pRun->GetDeadline(), &ftNow) < 0)
{
pRun->UnLink();
//
// If the system needed to stay awake to run this task, decrement
// the thread's wake count. (We know that this is always called
// by the worker thread.)
//
if (pRun->IsFlagSet(TASK_FLAG_SYSTEM_REQUIRED))
{
WrapSetThreadExecutionState(FALSE,
"CIdleRunList::FreeExpiredOrRegenerated");
}
delete pRun;
}
}
}
//+----------------------------------------------------------------------------
//
// Function: WrapSetThreadExecutionStateFn
//
// Synopsis: Wrapper for dynamically loaded function
//
//-----------------------------------------------------------------------------
void WINAPI
WrapSetThreadExecutionStateFn(
BOOL fSystemRequired
#if DBG
, LPCSTR pszDbgMsg // parameter for debug output message
#endif
)
{
DWORD dwCount = (DWORD) (ULONG_PTR)TlsGetValue(g_WakeCountSlot);
if (fSystemRequired)
{
//
// Increment this thread's keep-awake count. If it was zero,
// set the system-required state.
//
schDebugOut((DEB_USER5, "INCREMENTING keep-awake count to %ld: %s\n",
dwCount + 1, pszDbgMsg));
schAssert(dwCount != (DWORD) -1);
dwCount++;
if (dwCount == 1)
{
if (pfnSetThreadExecutionState != NULL)
{
schDebugOut((DEB_USER5, "SETTING sys-required state\n"));
(pfnSetThreadExecutionState)(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
}
}
}
else
{
//
// Decrement this thread's keep-awake count. If it becomes zero,
// reset the system-required state.
//
schDebugOut((DEB_USER5, "DECREMENTING keep-awake count to %ld: %s\n",
dwCount - 1, pszDbgMsg));
schAssert(dwCount != 0);
dwCount--;
if (dwCount == 0)
{
if (pfnSetThreadExecutionState != NULL)
{
schDebugOut((DEB_USER5, "RESETTING sys-required state\n"));
(pfnSetThreadExecutionState)(ES_CONTINUOUS);
}
}
}
if (!TlsSetValue(g_WakeCountSlot, UlongToPtr(dwCount)))
{
ERR_OUT("TlsSetValue", GetLastError());
}
}
//+----------------------------------------------------------------------------
//
// Function: InitThreadWakeCount
//
// Synopsis: Initialize this thread's keep-awake count.
//
//-----------------------------------------------------------------------------
void
InitThreadWakeCount()
{
schDebugOut((DEB_USER5, "INITIALIZING keep-awake count to 0\n"));
if (!TlsSetValue(g_WakeCountSlot, 0))
{
ERR_OUT("TlsSetValue", GetLastError());
}
}