|
|
/************************************************************************
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;
}
|