Source code of Windows XP (NT5)
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.
 
 
 
 
 
 

1049 lines
30 KiB

/************************************************************************
Copyright (c) 2000 - 2000 Microsoft Corporation
Module Name :
tasksched.cpp
Abstract :
Source file for task manager classes and routines.
Author :
Revision History :
***********************************************************************/
#include "stdafx.h"
#if !defined(BITS_V12_ON_NT4)
#include "tasksched.tmh"
#endif
////////////////////////////////////////////////////////////////////////////////////
//
// TaskSchedulerWorkItem
//
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
// Constructor/Destructor
////////////////////////////////////////////////////////////////////////////////////
TaskSchedulerWorkItem::TaskSchedulerWorkItem( FILETIME *pTimeToRun ) :
m_Container( NULL ),
m_CancelEvent(NULL),
m_ItemComplete(NULL),
m_ItemCanceled(NULL),
m_State(TASK_STATE_NOTHING),
m_WorkGroup(NULL)
{
try
{
// All events are manual reset.
m_ItemCanceled = CreateEvent( NULL, TRUE, FALSE, NULL );
if ( !m_ItemCanceled )
throw ComError( HRESULT_FROM_WIN32(GetLastError()));
// new items are complete
m_CancelEvent = CreateEvent( NULL, TRUE, TRUE, NULL );
if ( !m_CancelEvent )
throw ComError( HRESULT_FROM_WIN32(GetLastError()));
m_ItemComplete = CreateEvent( NULL, TRUE, FALSE, NULL );
if ( !m_ItemComplete )
throw ComError( HRESULT_FROM_WIN32(GetLastError()));
}
catch ( ComError Error )
{
this->~TaskSchedulerWorkItem();
throw;
}
}
TaskSchedulerWorkItem::~TaskSchedulerWorkItem()
{
if ( m_ItemComplete ) SetEvent( m_ItemComplete );
if ( m_CancelEvent ) CloseHandle( m_CancelEvent );
if ( m_ItemCanceled ) CloseHandle( m_ItemCanceled );
if ( m_ItemComplete ) CloseHandle( m_ItemComplete );
}
void
TaskSchedulerWorkItem::Serialize(
HANDLE hFile
)
{
//
// If this function changes, be sure that the metadata extension
// constants are adequate.
//
bool fActive = g_Manager->m_TaskScheduler.IsWorkItemInScheduler( this );
SafeWriteFile( hFile, fActive );
if (fActive)
{
SafeWriteFile( hFile, m_InsertionTime );
SafeWriteFile( hFile, m_TimeToRun );
}
}
void
TaskSchedulerWorkItem::Unserialize(
HANDLE hFile
)
{
bool fActive;
SafeReadFile( hFile, &fActive );
if (fActive)
{
SafeReadFile( hFile, &m_InsertionTime );
SafeReadFile( hFile, &m_TimeToRun );
LogTask("workitem %p : adding to scheduler for %I64d", this, FILETIMEToUINT64(m_TimeToRun) );
g_Manager->m_TaskScheduler.InsertWorkItem( this, &m_TimeToRun );
}
else
{
LogTask("workitem %p: not in scheduler", this );
}
}
////////////////////////////////////////////////////////////////////////////////////
//
// TaskScheduler
//
////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////
// Constructor/Destructor
////////////////////////////////////////////////////////////////////////////////////
TaskScheduler::TaskScheduler() :
m_bShouldDie(false),
m_WaitableTimer(NULL),
m_ReaderLock(NULL),
m_WriterSemaphore(NULL),
m_ReaderCount(0),
m_WorkItemTLS((DWORD)-1),
m_WriterOwner(0),
m_WorkerInitialized(NULL)
{
try
{
m_WorkItemTLS = TlsAlloc();
if ( (DWORD)-1 == m_WorkItemTLS)
throw ComError( HRESULT_FROM_WIN32(GetLastError()));
m_SchedulerLock = CreateMutex( NULL, FALSE, NULL );
if ( !m_SchedulerLock )
throw ComError( HRESULT_FROM_WIN32(GetLastError()));
m_WaitableTimer = CreateWaitableTimer( NULL, FALSE, NULL );
if ( !m_WaitableTimer )
throw ComError( HRESULT_FROM_WIN32(GetLastError()));
// Create and autoreset event for synchronization on startup
m_WorkerInitialized = CreateEvent( NULL, FALSE, FALSE, NULL );
if ( !m_WorkerInitialized )
throw ComError( HRESULT_FROM_WIN32(GetLastError()));
m_ReaderLock = CreateMutex( NULL, FALSE, NULL );
if ( !m_ReaderLock )
throw ComError( HRESULT_FROM_WIN32(GetLastError()));
m_WriterSemaphore = CreateSemaphore( NULL, 1, 1, NULL );
if ( !m_WriterSemaphore )
throw ComError( HRESULT_FROM_WIN32(GetLastError()));
}
catch ( ComError Error )
{
this->~TaskScheduler();
throw;
}
}
TaskScheduler::~TaskScheduler()
{
if ((DWORD)-1 != m_WorkItemTLS)
TlsFree( m_WorkItemTLS );
if ( m_SchedulerLock )
CloseHandle( m_SchedulerLock );
if ( m_WaitableTimer )
CloseHandle( m_WaitableTimer );
if ( m_WorkerInitialized )
CloseHandle( m_WorkerInitialized );
if ( m_ReaderLock )
CloseHandle( m_ReaderLock );
if ( m_WriterSemaphore )
CloseHandle( m_WriterSemaphore );
}
//////////////////////////////////////////////////////////////////////////////////////////
// WorkItem control
//////////////////////////////////////////////////////////////////////////////////////////
bool TaskScheduler::CancelWorkItem( TaskSchedulerWorkItem * pWorkItem )
{
LogTask( "cancelling %p", pWorkItem );
RTL_VERIFY( WAIT_OBJECT_0 == WaitForSingleObject( m_SchedulerLock, INFINITE ) );
HANDLE hHandles[2];
hHandles[0] = pWorkItem->m_ItemCanceled;
hHandles[1] = pWorkItem->m_ItemComplete;
DWORD dwResult = WaitForMultipleObjects( 2, hHandles, FALSE, 0 );
if ( (WAIT_OBJECT_0 == dwResult) ||
((WAIT_OBJECT_0 + 1) == dwResult ) )
{
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
return true; // Job completed before the cancel
}
// If canceling the current work item, call Acknowlege immedialtly
if ( GetCurrentWorkItem() == pWorkItem )
{
LogTask( "Canceling work item %p, we are the owner", pWorkItem );
RTL_VERIFY( SetEvent( pWorkItem->m_CancelEvent ) );
AcknowledgeWorkItemCancel();
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
return false; // Job canceled
}
//
// Remove the work item from its list.
//
switch( pWorkItem->m_State )
{
case TASK_STATE_WAITING:
{
m_WaitingList.erase( *pWorkItem );
pWorkItem->m_State = TASK_STATE_CANCELED;
pWorkItem->m_WorkGroup = NULL;
Reschedule();
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
return false;
}
case TASK_STATE_READY:
{
TaskSchedulerWorkGroup *pGroup =
static_cast<TaskSchedulerWorkGroup*>(pWorkItem->m_WorkGroup);
pGroup->m_ReadyList.erase( *pWorkItem );
// Kill one on the semaphore
RTL_VERIFY( WAIT_OBJECT_0 == WaitForSingleObject( pGroup->m_ItemAvailableSemaphore, 0 ) );
pWorkItem->m_State = TASK_STATE_CANCELED;
pWorkItem->m_WorkGroup = NULL;
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
return false;
}
case TASK_STATE_RUNNING:
{
// cancelling on another thread
RTL_VERIFY( SetEvent( pWorkItem->m_CancelEvent ) );
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
dwResult = WaitForMultipleObjects( 2, hHandles, FALSE, INFINITE );
ASSERT( ( WAIT_OBJECT_0 == dwResult ) || ( WAIT_OBJECT_0 + 1 == dwResult ) );
return WAIT_OBJECT_0 != dwResult;
}
case TASK_STATE_CANCELED:
case TASK_STATE_COMPLETE:
case TASK_STATE_NOTHING:
default:
ASSERT( TASK_STATE_CANCELED == pWorkItem->m_State ||
TASK_STATE_COMPLETE == pWorkItem->m_State ||
TASK_STATE_NOTHING == pWorkItem->m_State );
ASSERT( NULL == pWorkItem->m_WorkGroup );
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
return true;
}
}
void TaskScheduler::CompleteWorkItem( bool bCancel )
{
RTL_VERIFY( WaitForSingleObject( m_SchedulerLock, INFINITE ) == WAIT_OBJECT_0 );
TaskSchedulerWorkItem *pWorkItem = GetCurrentWorkItem();
LogTask( "completing %p", pWorkItem );
// ASSERT( pWorkItem );
if (pWorkItem)
{
RTL_VERIFY( TlsSetValue( m_WorkItemTLS, NULL ) );
TaskSchedulerWorkGroup *pGroup =
static_cast<TaskSchedulerWorkGroup*>(pWorkItem->m_WorkGroup);
pGroup->m_RunningList.erase( *pWorkItem );
pWorkItem->m_WorkGroup = NULL;
pWorkItem->m_State = bCancel ? TASK_STATE_CANCELED : TASK_STATE_COMPLETE;
RTL_VERIFY( SetEvent( bCancel ? pWorkItem->m_ItemCanceled : pWorkItem->m_ItemComplete ) );
}
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
}
void TaskScheduler::DispatchWorkItem()
{
TaskSchedulerWorkItem *pWorkItem = NULL;
RTL_VERIFY( WaitForSingleObject( m_SchedulerLock, INFINITE ) == WAIT_OBJECT_0 );
// Move all the jobs that are available from waiting
// to ready
while ( !m_WaitingList.empty() )
{
FILETIME ftCurrentTime;
GetSystemTimeAsFileTime( &ftCurrentTime );
TaskSchedulerWorkItem * pHeadItem = &(*m_WaitingList.begin());
UINT64 CurrentTime = FILETIMEToUINT64( ftCurrentTime );
UINT64 HeadTime = FILETIMEToUINT64( pHeadItem->m_TimeToRun );
if ( HeadTime > CurrentTime )
{
// All the jobs in the list are still waiting,
// let them continue waiting
break;
}
// transfer the head work item from the waiting list
// to the ready list of the correct work group
m_WaitingList.erase( *pHeadItem );
AddItemToWorkGroup( pHeadItem->GetSid(), pHeadItem );
}
Reschedule();
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
}
void
TaskScheduler::InsertDelayedWorkItem(
TaskSchedulerWorkItem *pWorkItem,
UINT64 Delay100Nsec
)
{
FILETIME ftCurrentTime;
GetSystemTimeAsFileTime( &ftCurrentTime );
UINT64 TimeToRun = Delay100Nsec + FILETIMEToUINT64( ftCurrentTime );
FILETIME ftTimeToRun = UINT64ToFILETIME( TimeToRun );
InsertWorkItem( pWorkItem, &ftTimeToRun );
}
void
TaskScheduler::RescheduleDelayedTask(
TaskSchedulerWorkItem *pWorkItem,
UINT64 Delay100Nsec
)
{
// Resets the time for the work item to run to be Delay100NSec after
// the insertion time.
// If the work item is not in the queue, running, completed,
// or canceled then this operation is ignored.
// Otherwise, the job is rescheduled.
LogTask( "rescheduling %p", pWorkItem );
RTL_VERIFY( WaitForSingleObject( m_SchedulerLock, INFINITE ) == WAIT_OBJECT_0 );
// If the work item is not on a running list or the pending list,
// ignore the call.
if ( TASK_STATE_READY == pWorkItem->m_State )
{
TaskSchedulerWorkGroup *pGroup =
static_cast<TaskSchedulerWorkGroup*>( pWorkItem->m_WorkGroup );
pGroup->m_ReadyList.erase( *pWorkItem );
RTL_VERIFY( WAIT_OBJECT_0 == WaitForSingleObject( pGroup->m_ItemAvailableSemaphore, 0 ) );
}
else if ( TASK_STATE_WAITING == pWorkItem->m_State )
{
m_WaitingList.erase( *pWorkItem );
}
else
{
LogTask( "item %p not pending. Ignoring.", pWorkItem );
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
return;
}
UINT64 TimeToRun = Delay100Nsec + FILETIMEToUINT64( pWorkItem->m_InsertionTime );
pWorkItem->m_TimeToRun = UINT64ToFILETIME( TimeToRun );
m_WaitingList.insert( *pWorkItem );
pWorkItem->m_State = TASK_STATE_WAITING;
pWorkItem->m_WorkGroup = NULL;
Reschedule();
LogTask( "item %p rescheduled", pWorkItem );
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
}
inline INT64 abs(INT64 x)
{
if (x >= 0)
{
return x;
}
else
{
return -x;
}
}
void TaskScheduler::InsertWorkItem( TaskSchedulerWorkItem *pWorkItem, FILETIME *pTimeToRun )
{
{
INT64 Difference;
FILETIME ftCurrentTime;
GetSystemTimeAsFileTime( &ftCurrentTime );
if (pTimeToRun)
{
Difference = INT64(FILETIMEToUINT64( *pTimeToRun )) - INT64(FILETIMEToUINT64( ftCurrentTime ));
if (abs(Difference) > 86400 * NanoSec100PerSec)
{
LogTask( "inserting %p; activates in %f days", pWorkItem, float(Difference) / (float(NanoSec100PerSec) * 86400) );
}
else
{
LogTask( "inserting %p; activates in %f seconds", pWorkItem, float(Difference) / float(NanoSec100PerSec) );
}
}
else
{
LogTask( "inserting %p; activates now", pWorkItem );
}
}
RTL_VERIFY( WaitForSingleObject( m_SchedulerLock, INFINITE ) == WAIT_OBJECT_0 );
GetSystemTimeAsFileTime( &pWorkItem->m_InsertionTime );
RTL_VERIFY( ResetEvent( pWorkItem->m_CancelEvent ) );
RTL_VERIFY( ResetEvent( pWorkItem->m_ItemComplete ) );
RTL_VERIFY( ResetEvent( pWorkItem->m_ItemCanceled ) );
if ( !pTimeToRun && !m_bShouldDie )
{
pWorkItem->m_TimeToRun = pWorkItem->m_InsertionTime;
AddItemToWorkGroup( pWorkItem->GetSid(), pWorkItem );
}
else
{
if (pTimeToRun)
{
pWorkItem->m_TimeToRun = *pTimeToRun;
}
else
{
GetSystemTimeAsFileTime( &pWorkItem->m_TimeToRun );
}
pWorkItem->m_State = TASK_STATE_WAITING;
m_WaitingList.insert( *pWorkItem );
Reschedule();
}
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
}
bool TaskScheduler::IsWorkItemInScheduler( TaskSchedulerWorkItem *pWorkItem )
{
bool b;
RTL_VERIFY( WaitForSingleObject( m_SchedulerLock, INFINITE ) == WAIT_OBJECT_0 );
b = ( TASK_STATE_WAITING == pWorkItem->m_State ||
TASK_STATE_READY == pWorkItem->m_State ||
TASK_STATE_RUNNING == pWorkItem->m_State );
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
return b;
}
void TaskScheduler::Reschedule()
{
if ( m_WaitingList.empty() )
{
// Nothing to do, cancel waitable timer.
RTL_VERIFY( CancelWaitableTimer( m_WaitableTimer ) );
return;
}
LARGE_INTEGER NextItemTime;
FILETIME ftNextItemTime = (*m_WaitingList.begin()).m_TimeToRun;
NextItemTime.QuadPart = (INT64)FILETIMEToUINT64( ftNextItemTime );
RTL_VERIFY(
SetWaitableTimer(
m_WaitableTimer,
&NextItemTime,
0,
NULL,
NULL,
FALSE ) );
}
/////////////////////////////////////////////////////////////////////////////////////////////////
// Reader/Writer lock
//
// Algorithm:
//
// Writer:
// Wait on writer lock and cancel event. Return when either is signaled
//
// Unlock writer:
// Release the writer lock
//
// Lock reader:
// Lock reader lock to protect count. If I am the first reader, grab the writer semaphore.
// Unlock reader lock. If on either wait the cancel event is signaled, abort.
//
// Unlock reader:
// Decrement the reader count. If last reader, release the writer lock.
//
/////////////////////////////////////////////////////////////////////////////////////////////////
bool TaskScheduler::LockReader()
{
LogLock( "reader" );
HANDLE hCancel = GetCancelEvent();
if ( !hCancel )
{
RTL_VERIFY( WaitForSingleObject( m_ReaderLock, INFINITE ) == WAIT_OBJECT_0 );
// InterlockedIncrement returns the new value
if ( InterlockedIncrement( &m_ReaderCount ) == 1 )
{
RTL_VERIFY( WaitForSingleObject( m_WriterSemaphore, INFINITE ) == WAIT_OBJECT_0 );
}
RTL_VERIFY( ReleaseMutex( m_ReaderLock ) );
LogLock("reader lock acquired");
ASSERT( !m_WriterOwner );
return false;
}
DWORD dwResult;
HANDLE hReaderLockHandles[2];
hReaderLockHandles[0] = hCancel;
hReaderLockHandles[1] = m_ReaderLock;
dwResult = WaitForMultipleObjects( 2, hReaderLockHandles, false, INFINITE );
switch ( dwResult )
{
case WAIT_OBJECT_0 + 0:
// cancel request
LogLock( "Cancel requested, aborting read lock" );
return true;
case WAIT_OBJECT_0 + 1:
// lock acquired
break;
default:
ASSERT(0);
}
bool bReturnVal = false;
ULONG NewReaderCount = InterlockedIncrement( &m_ReaderCount );
if (1 == NewReaderCount )
{
LogLock("First reader, need to block writers");
HANDLE hWriterLockHandles[2];
hWriterLockHandles[0] = hCancel;
hWriterLockHandles[1] = m_WriterSemaphore;
dwResult = WaitForMultipleObjects( 2, hWriterLockHandles, false, INFINITE );
switch ( dwResult )
{
case WAIT_OBJECT_0 + 0:
// cancel request
LogLock( "Cancel requested, aborting acquire of writer lock");
bReturnVal = true;
case WAIT_OBJECT_0 + 1:
// lock acquired
break;
default:
ASSERT(0);
}
}
RTL_VERIFY( ReleaseMutex( m_ReaderLock ) );
if (!bReturnVal)
{
LogLock("reader lock acquired");
ASSERT( !m_WriterOwner );
}
return bReturnVal;
}
void TaskScheduler::UnlockReader()
{
LogLock( "reader unlock" );
LONG lNewReaderCount = InterlockedDecrement( &m_ReaderCount );
ASSERT( lNewReaderCount >= 0 );
if (!lNewReaderCount ) //Last reader
{
LogLock( "Last reader, letting writers pass" );
RTL_VERIFY( ReleaseSemaphore( m_WriterSemaphore, 1, NULL ) );
}
LogLock( "Unlocked read access to lock" );
}
bool TaskScheduler::LockWriter()
{
LogLock( "writer lock" );
HANDLE hCancel = GetCancelEvent();
if (!hCancel)
{
RTL_VERIFY( WaitForSingleObject( m_WriterSemaphore, INFINITE ) == WAIT_OBJECT_0 );
ASSERT( !m_WriterOwner );
m_WriterOwner = GetCurrentThreadId();
LogLock("Lock acquired with write access");
return false;
}
HANDLE hHandles[2];
hHandles[0] = hCancel;
hHandles[1] = m_WriterSemaphore;
DWORD dwResult = WaitForMultipleObjects( 2, hHandles, false, INFINITE );
switch ( dwResult )
{
case WAIT_OBJECT_0 + 0:
// cancel request
LogLock("Cancel requested, aborting lock with write access");
return true;
case WAIT_OBJECT_0 + 1:
// lock acquired
ASSERT( !m_WriterOwner );
m_WriterOwner = GetCurrentThreadId();
LogLock("Lock acquired with write access");
return false;
default:
ASSERT(0);
return false;
}
}
void TaskScheduler::UnlockWriter()
{
LogLock( "writer unlock" );
ASSERT( GetCurrentThreadId() == m_WriterOwner );
m_WriterOwner = 0;
RTL_VERIFY( ReleaseSemaphore( m_WriterSemaphore, 1, NULL ) );
LogLock("Unlocked lock with write access");
}
TaskScheduler::TaskSchedulerWorkGroup::TaskSchedulerWorkGroup(
SidHandle Sid ) :
m_Sid(Sid),
m_ItemAvailableSemaphore(NULL),
m_Threads(0),
m_BusyThreads(0)
{
memset( m_Thread, 0, sizeof( m_Thread ) );
memset( m_ThreadId, 0, sizeof( m_ThreadId ) );
m_ItemAvailableSemaphore =
CreateSemaphore(
NULL,
0, // InitialCount
0x7FFFFFFF, // MaxCount
NULL );
if ( !m_ItemAvailableSemaphore )
throw ComError( HRESULT_FROM_WIN32( GetLastError() ) );
}
TaskScheduler::TaskSchedulerWorkGroup::~TaskSchedulerWorkGroup()
{
if ( m_ItemAvailableSemaphore )
CloseHandle( m_ItemAvailableSemaphore );
}
void
TaskScheduler::AddItemToWorkGroup(
SidHandle Sid,
TaskSchedulerWorkItem *pWorkItem )
{
// If the work group has alread been created,
// don't create it again
WorkGroupMapType::iterator i = m_WorkGroupMap.find( Sid );
TaskSchedulerWorkGroup *pWorkGroup = NULL;
if ( m_WorkGroupMap.end() != i )
{
pWorkGroup = (*i).second;
}
else
{
LogTask( "Creating a new work group" );
while(1)
{
try
{
pWorkGroup = new TaskSchedulerWorkGroup( Sid );
m_WorkGroupMap.insert( WorkGroupMapType::value_type( Sid, pWorkGroup ) );
LogTask( "Created new workgroup %p", pWorkGroup );
break;
}
catch( ComError Error )
{
LogError( "Unable to create new workgroup sleeping, error %!winerr!", Error.Error() );
m_WorkGroupMap.erase( Sid );
delete pWorkGroup;
pWorkGroup = NULL;
Sleep( 5000 );
}
}
}
LogInfo( "Adding %p to workgroup %p", pWorkItem, pWorkGroup );
pWorkGroup->m_ReadyList.insert( *pWorkItem );
pWorkItem->m_State = TASK_STATE_READY;
pWorkItem->m_WorkGroup = pWorkGroup;
RTL_VERIFY( ReleaseSemaphore( pWorkGroup->m_ItemAvailableSemaphore, 1, NULL ) );
// use a very aproximative heuristic to determine when to add more threads.
// The load is the number of work items that are ready to run plus the number
// of items being worked on(busy threads). See the note below why the number of
// ready work items is not a good estimate.
size_t Load = pWorkGroup->m_ReadyList.size() + pWorkGroup->m_BusyThreads;
if ( Load > pWorkGroup->m_Threads &&
pWorkGroup->m_Threads < MAX_WORKGROUP_THREADS )
{
LogInfo( "load of %u and %u threads. Add another thread",
Load, pWorkGroup->m_Threads );
while(1)
{
m_NewWorkerGroup = pWorkGroup;
ASSERT( m_WorkGroupMap.end() != m_WorkGroupMap.find( m_NewWorkerGroup->m_Sid ) );
RTL_VERIFY( ResetEvent( m_WorkerInitialized ) );
HANDLE & ThreadHandle = pWorkGroup->m_Thread[ pWorkGroup->m_Threads ];
DWORD & ThreadId = pWorkGroup->m_ThreadId[ pWorkGroup->m_Threads ];
ThreadHandle =
CreateThread(
NULL, // security descriptor
0, // Use default stack
TaskScheduler::WorkGroupWorkerThunk,
static_cast<LPVOID>( this ),
0,
&ThreadId );
if ( !ThreadHandle )
{
LogError( "Unable to create new worker, error %!winerr!", GetLastError() );
Sleep( 5000 );
continue;
}
LogTask( "Created new worker with a handle %p, ID %u", ThreadHandle, ThreadId );
HANDLE WaitHandles[2] = { ThreadHandle, m_WorkerInitialized };
DWORD dwResult =
WaitForMultipleObjectsEx(
2,
WaitHandles,
FALSE,
INFINITE,
FALSE );
switch( dwResult )
{
case WAIT_OBJECT_0:
GetExitCodeThread( ThreadHandle, &dwResult );
LogError( "Thread exited with code %!winerr!, sleeping", dwResult );
CloseHandle( ThreadHandle );
ThreadHandle = 0;
ThreadId = 0;
Sleep( 5000 );
continue;
case WAIT_OBJECT_0 + 1:
break;
default:
LogError( "Unexpected error, %!winerr!", dwResult );
ASSERT( 0 );
}
LogTask( "Worker signaled success" );
m_NewWorkerGroup = NULL;
pWorkGroup->m_Threads++;
break;
}
}
}
void
TaskScheduler::KillBackgroundTasks()
{
LogTask( "Killing background threads" );
RTL_VERIFY( WaitForSingleObject( m_SchedulerLock, INFINITE ) == WAIT_OBJECT_0 );
m_bShouldDie = TRUE;
DWORD Result;
while(1)
{
if ( m_WorkGroupMap.empty() )
{
LogTask( "No more work groups, all done" );
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
return;
}
TaskSchedulerWorkGroup *pGroup = (*m_WorkGroupMap.begin()).second;
RTL_VERIFY( ReleaseSemaphore( pGroup->m_ItemAvailableSemaphore, pGroup->m_Threads, NULL ) );
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
Result = WaitForMultipleObjects( pGroup->m_Threads, pGroup->m_Thread, TRUE, INFINITE );
// WAIT_OBJECT_0 == 0 so Result >= WAIT_OBJECT_0 is always true
ASSERT( Result < WAIT_OBJECT_0 + pGroup->m_Threads );
RTL_VERIFY( WaitForSingleObject( m_SchedulerLock, INFINITE ) == WAIT_OBJECT_0 );
for(size_t c=0; c < pGroup->m_Threads; c++ )
{
CloseHandle( pGroup->m_Thread[c] );
}
m_WorkGroupMap.erase( pGroup->m_Sid );
delete pGroup;
LogTask( "Killed everyone in work group %p", pGroup );
}
}
DWORD BackgroundThreadProcFilter(
LPEXCEPTION_POINTERS ExceptionPointers );
DWORD
TaskScheduler::WorkGroupWorkerThunk( void *pContext )
{
__try
{
return
static_cast<TaskScheduler*>( pContext )->WorkGroupWorker();
}
__except( BackgroundThreadProcFilter(
GetExceptionInformation() ) )
{
ASSERT( 0 );
}
ASSERT( 0 );
return 0;
}
DWORD
TaskScheduler::WorkGroupWorker( )
{
HRESULT Hr;
LogTask( "I'm alive!" );
Hr = CoInitializeEx( NULL, COINIT_MULTITHREADED );
if ( FAILED( Hr ) )
{
LogError( "CoInitializeEx failed, %!winerr!", Hr );
return (DWORD)(Hr);
}
TaskSchedulerWorkGroup *pGroup = m_NewWorkerGroup;
ASSERT( m_WorkGroupMap.end() != m_WorkGroupMap.find( pGroup->m_Sid ) );
RTL_VERIFY( SetEvent( m_WorkerInitialized ) );
LogTask( "Initialization complete" );
while(1)
{
TaskSchedulerWorkItem *pWorkItem = NULL;
HANDLE Handles[] = { pGroup->m_ItemAvailableSemaphore, m_SchedulerLock };
DWORD dwWaitResult =
WaitForMultipleObjectsEx(
sizeof(Handles)/sizeof(*Handles),
Handles,
TRUE, // Wait for all events
30000,
FALSE ); // ablertable wait
switch( dwWaitResult )
{
case WAIT_OBJECT_0:
case WAIT_OBJECT_0+1:
break;
case WAIT_TIMEOUT:
{
LogInfo( "Timeout expired, check if we have something to do");
RTL_VERIFY( WaitForSingleObject( m_SchedulerLock, INFINITE ) == WAIT_OBJECT_0 );
if ( pGroup->m_ReadyList.empty() )
{
goto cleanup_on_timeout;
}
else
{
LogTask( "Still stuff to do, stay alive" );
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
continue;
}
}
default:
ASSERT(0);
}
if ( m_bShouldDie )
{
LogTask( "Ordered to die, do so" );
goto dodie;
}
ASSERT( !pGroup->m_ReadyList.empty() );
// Get first item in ready list and move
// it over to running list.
pWorkItem = &(*pGroup->m_ReadyList.begin());
pGroup->m_ReadyList.erase( *pWorkItem );
pGroup->m_RunningList.insert( *pWorkItem );
pWorkItem->m_State = TASK_STATE_RUNNING;
ASSERT( pGroup == pWorkItem->m_WorkGroup );
// Mark this thread as busy
// NOTE: This counter is needed because some
// code marks work items as complete even though
// the really arn't complete yet. So we need
// to have this to indicatate has many threads
// are really available.
InterlockedIncrement( &pGroup->m_BusyThreads );
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
// Now do the real dispatching
LogTask( "dispatching %p", pWorkItem );
RTL_VERIFY( TlsSetValue( m_WorkItemTLS, pWorkItem ) );
pWorkItem->OnDispatch();
if (GetCurrentWorkItem())
CompleteWorkItem();
// Mark this thread as free
InterlockedDecrement( &pGroup->m_BusyThreads );
}
cleanup_on_timeout:
if ( 1 == pGroup->m_Threads )
{
// If were the last thread, destroy the workgroup
LogTask( "We are the only thread, destroy work group %p", pGroup );
CloseHandle( pGroup->m_Thread[0] );
WorkGroupMapType::iterator i = m_WorkGroupMap.find( pGroup->m_Sid );
ASSERT( m_WorkGroupMap.end() != i );
m_WorkGroupMap.erase( i );
delete pGroup;
}
else
{
// we were not the last thread, so remove ourselves from the list.
// First, find the slot for this thread.
size_t index = 0;
for (;index < pGroup->m_Threads; index++ )
{
if ( GetCurrentThreadId() == pGroup->m_ThreadId[index] )
break;
}
ASSERT( index < pGroup->m_Threads );
LogTask( "We are not the only thread, remove thread in slot %u", index );
CloseHandle( pGroup->m_Thread[index] );
// collapse the list
size_t slots = pGroup->m_Threads - index - 1;
memmove( &pGroup->m_Thread[index], &pGroup->m_Thread[index+1], slots * sizeof(*pGroup->m_Thread) );
memmove( &pGroup->m_ThreadId[index], &pGroup->m_ThreadId[index+1], slots * sizeof(*pGroup->m_ThreadId) );
pGroup->m_Threads--;
pGroup->m_Thread[pGroup->m_Threads] = 0;
pGroup->m_ThreadId[pGroup->m_Threads] = 0;
}
dodie:
RTL_VERIFY( ReleaseMutex( m_SchedulerLock ) );
CoUninitialize();
return 0;
}