|
|
#include "priv.h"
#include "schedule.h"
// debug stuff for tracking critical section owners.....
#ifdef DEBUG
#define DECLARE_CRITICAL_SECTION(x) CRITICAL_SECTION x; \
DWORD dwThread##x;
#define STATIC_DECLARE_CRITICAL_SECTION(x) static CRITICAL_SECTION x; \
static DWORD dwThread##x;
#define STATIC_INIT_CRITICAL_SECTION(c,x) CRITICAL_SECTION c::x = {0}; \
DWORD c::dwThread##x;
#define ASSERT_CRITICAL_SECTION(x) ASSERT( dwThread##x == GetCurrentThreadId() );
#define OBJECT_ASSERT_CRITICAL_SECTION(o,x) ASSERT( o->dwThread##x == GetCurrentThreadId() );
#define ENTER_CRITICAL_SECTION(x) EnterCriticalSection(&x); \
dwThread##x = GetCurrentThreadId();
#define OBJECT_ENTER_CRITICAL_SECTION(o,x) EnterCriticalSection(&o->x); \
o->dwThread##x = GetCurrentThreadId();
#define LEAVE_CRITICAL_SECTION(x) ASSERT_CRITICAL_SECTION(x); \
LeaveCriticalSection(&x);
#define OBJECT_LEAVE_CRITICAL_SECTION(o,x) OBJECT_ASSERT_CRITICAL_SECTION(o,x); \
LeaveCriticalSection(&o->x); #else
#define DECLARE_CRITICAL_SECTION(x) CRITICAL_SECTION x;
#define STATIC_DECLARE_CRITICAL_SECTION(x) static CRITICAL_SECTION x;
#define STATIC_INIT_CRITICAL_SECTION(c,x) CRITICAL_SECTION c::x = {0};
#define ASSERT_CRITICAL_SECTION(x)
#define OBJECT_ASSERT_CRITICAL_SECTION(o,x)
#define ENTER_CRITICAL_SECTION(x) EnterCriticalSection(&x);
#define OBJECT_ENTER_CRITICAL_SECTION(o,x) EnterCriticalSection(&o->x);
#define LEAVE_CRITICAL_SECTION(x) LeaveCriticalSection(&x);
#define OBJECT_LEAVE_CRITICAL_SECTION(o,x) LeaveCriticalSection(&o->x);
#endif
#define TF_SCHEDULER 0x20
// struct to hold the details for each task that is to be executed....
struct TaskNode { LPRUNNABLETASK pTask; TASKOWNERID toid; DWORD dwPriority; DWORD_PTR dwLParam; BOOL fSuspended; };
class CShellTaskScheduler : public IShellTaskScheduler2 { public: CShellTaskScheduler( HRESULT * pHr ); ~CShellTaskScheduler();
STDMETHOD (QueryInterface) (REFIID riid, LPVOID * ppvObj ); STDMETHOD_(ULONG, AddRef)( void ); STDMETHOD_(ULONG,Release)( void );
STDMETHOD (AddTask)(IRunnableTask * pTask, REFTASKOWNERID rtoid, DWORD_PTR lParam, DWORD dwPriority ); STDMETHOD (RemoveTasks)( REFTASKOWNERID rtoid, DWORD_PTR dwLParam, BOOL fWaitIfRunning ); STDMETHOD (Status)( DWORD dwStatus, DWORD dwThreadTimeout ); STDMETHOD_(UINT, CountTasks)(REFTASKOWNERID rtoid);
STDMETHOD (AddTask2)(IRunnableTask * pTask, REFTASKOWNERID rtoid, DWORD_PTR lParam, DWORD dwPriority, DWORD grfFlags); STDMETHOD (MoveTask)(REFTASKOWNERID rtoid, DWORD_PTR dwLParam, DWORD dwPriority, DWORD grfFlags );
protected:
// data held by a task scheduler to refer to the current worker that it has....
struct WorkerData { BOOL Init(CShellTaskScheduler *pts);
// this (pThis) is used to pass the controlling
// object back and forth to the thread, so that threads can be moved
// back and forth from objects as they need them.
CShellTaskScheduler * pThis;
#ifdef DEBUG
DWORD dwThreadID; #endif
};
friend UINT CShellTaskScheduler_ThreadProc( LPVOID pParam ); friend int CALLBACK ListDestroyCallback( LPVOID p, LPVOID pData );
VOID _KillScheduler( BOOL bKillCurTask ); BOOL _WakeScheduler( void );
BOOL _RemoveTasksFromList( REFTASKOWNERID rtoid, DWORD_PTR dwLParam );
// create a worker thread data block that can be associated with a task scheduler....
WorkerData * FetchWorker( void );
// from a worker thread, let go of the scheduler it is associated...
static BOOL ReleaseWorker( WorkerData * pThread );
/***********PERINSTANCE DATA ************/ DECLARE_CRITICAL_SECTION( m_csListLock ) HDPA m_hTaskList;
WorkerData * m_pWorkerThread;
// the currently running task...
TaskNode * m_pRunning;
// a semaphore that counts, so that all waiters canbe released...
HANDLE m_hCurTaskEnded;
DWORD m_dwStatus;
int m_iSignalCurTask; // - tell the thread to signal when the
// current task is finished if non-zero
// the other thread will signal the
// handle as many times as this variable
// holds.
BOOL m_fEmptyQueueAndSleep; // - tell the thread to empty itself and
// go to sleep (usually it is dying....
int m_iGoToSleep; // - tell the tread to go to sleep without emptying the queue
long m_cRef;
#ifdef DEBUG
void AssertForNoOneWaiting( void ) { // no one should be queued for waiting
ASSERT( m_iSignalCurTask == 0 );
// release the semaphore by zero to get the current count....
LONG lPrevCount = 0; ReleaseSemaphore( m_hCurTaskEnded, 0, &lPrevCount ); ASSERT( lPrevCount == 0 ); }; #endif
void IWantToKnowWhenCurTaskDone( void ) { m_iSignalCurTask ++; }; };
// private messages sent to the scheduler thread...
#define WM_SCH_WAKEUP WM_USER + 0x600
#define WM_SCH_TERMINATE WM_USER + 0x601
STDAPI CShellTaskScheduler_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi) { if ( pUnkOuter ) { return CLASS_E_NOAGGREGATION; }
HRESULT hr = NOERROR; CShellTaskScheduler * pScheduler = new CShellTaskScheduler( & hr ); if ( !pScheduler ) { return E_OUTOFMEMORY; } if ( FAILED( hr )) { delete pScheduler; return hr; }
*ppunk = SAFECAST(pScheduler, IShellTaskScheduler *); return NOERROR; }
// Global ExplorerTaskScheduler object that is used by multiple components.
IShellTaskScheduler * g_pTaskScheduler = NULL;
// This is the class factory routine for creating the one and only ExplorerTaskScheduler object.
// We have a static object (g_pTaskScheduler) that everyone who wants to use it shares.
STDAPI CSharedTaskScheduler_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppunk, LPCOBJECTINFO poi) { HRESULT hr = NOERROR;
if (pUnkOuter) return CLASS_E_NOAGGREGATION;
ENTERCRITICAL; if (g_pTaskScheduler) { g_pTaskScheduler->AddRef(); } else { hr = CShellTaskScheduler_CreateInstance(NULL, (LPUNKNOWN*)&g_pTaskScheduler, NULL); if (SUCCEEDED(hr)) { // set timeout to be 1 minute.....
g_pTaskScheduler->Status( ITSSFLAG_KILL_ON_DESTROY, 1 * 60 * 1000 );
// keep an additional ref for us..
g_pTaskScheduler->AddRef(); } }
*ppunk = SAFECAST(g_pTaskScheduler, IShellTaskScheduler*); LEAVECRITICAL;
return hr; }
STDAPI SHIsThereASystemScheduler( void ) { return ( g_pTaskScheduler ? S_OK : S_FALSE ); }
// use CoCreateInstance - thread pool removes need for global scheduler
STDAPI SHGetSystemScheduler( LPSHELLTASKSCHEDULER * ppScheduler ) { if ( !ppScheduler ) { return E_INVALIDARG; }
return CSharedTaskScheduler_CreateInstance(NULL, (IUnknown **)ppScheduler, NULL ); }
// use CoCreateInstance - thread pool removes need for global scheduler
STDAPI SHFreeSystemScheduler( void ) { TraceMsg(TF_SCHEDULER, "SHfss: g_pTaskSched=%x", g_pTaskScheduler);
IShellTaskScheduler * pSched;
ENTERCRITICAL; pSched = g_pTaskScheduler; g_pTaskScheduler = NULL; LEAVECRITICAL; if ( pSched ) { // assume the scheduler is empty....
pSched->RemoveTasks( TOID_NULL, ITSAT_DEFAULT_LPARAM, FALSE );
pSched->Release(); } return NOERROR; }
#ifdef DEBUG
STDAPI_(void) SHValidateEmptySystemScheduler() { if ( g_pTaskScheduler ) { ASSERT( g_pTaskScheduler->CountTasks( TOID_NULL ) == 0 ); } } #endif
int InsertInPriorityOrder( HDPA hTaskList, TaskNode * pNewNode, BOOL fBefore ); int CALLBACK ListDestroyCallback( LPVOID p, LPVOID pData ) { ASSERT( p != NULL ); if ( ! p ) { TraceMsg( TF_ERROR, "ListDestroyCallback() - p is NULL!" ); return TRUE; }
CShellTaskScheduler * pThis = (CShellTaskScheduler *) pData; ASSERT( pThis ); if ( ! pThis ) { TraceMsg( TF_ERROR, "ListDestroyCallback() - pThis is NULL!" ); return TRUE; }
TaskNode * pNode = (TaskNode *) p; ASSERT( pNode != NULL ); ASSERT( pNode->pTask != NULL );
#ifdef DEBUG
if ( pThis->m_pWorkerThread ) { // notify the thread that we are emptying the list from here, so remove these
// items from its mem track list
} #endif
// if it is suspended, kill it. If it is not suspended, then it has
// probably never been started..
if ( pNode->fSuspended ) { pNode->pTask->Kill( pThis->m_dwStatus == ITSSFLAG_COMPLETE_ON_DESTROY ); } pNode->pTask->Release(); delete pNode;
return TRUE; }
STDMETHODIMP CShellTaskScheduler::QueryInterface( REFIID riid, LPVOID * ppvObj ) { static const QITAB qit[] = { QITABENT(CShellTaskScheduler, IShellTaskScheduler), QITABENT(CShellTaskScheduler, IShellTaskScheduler2), { 0 }, };
return QISearch(this, qit, riid, ppvObj); }
STDMETHODIMP_ (ULONG) CShellTaskScheduler::AddRef() { InterlockedIncrement( &m_cRef ); return m_cRef; } STDMETHODIMP_ (ULONG) CShellTaskScheduler::Release() { if (0 == m_cRef) { AssertMsg(0, TEXT("CShellTaskScheduler::Release called too many times!")); return 0; }
if (InterlockedDecrement( &m_cRef ) == 0) { delete this; return 0; } return m_cRef; }
CShellTaskScheduler::CShellTaskScheduler( HRESULT * pHr) : m_cRef(1) { InitializeCriticalSection( &m_csListLock );
ASSERT(m_pWorkerThread == NULL); ASSERT(m_pRunning == NULL);
m_dwStatus = ITSSFLAG_COMPLETE_ON_DESTROY;
// grow queue by five each time...
m_hTaskList = DPA_Create( 5 ); if ( !m_hTaskList ) { *pHr = E_OUTOFMEMORY; }
m_hCurTaskEnded = CreateSemaphoreWrap( NULL, 0, 0xffff, NULL ); if ( !m_hCurTaskEnded ) { *pHr = E_FAIL; }
DllAddRef();
}
CShellTaskScheduler::~CShellTaskScheduler() { // if we don't have a tasklist and semaphore (constructor failure), we can't have a workerthread
ASSERT((m_hTaskList && m_hCurTaskEnded) || !m_pWorkerThread);
// but if we have a task list...
if ( m_hTaskList ) { EnterCriticalSection( &m_csListLock );
// if we have a background worker thread, then it MUST be doing something as we
// are now in the crit section so it can't go away
if ( m_pWorkerThread ) { // we tell the object we need to know when it has done with its stuff....
// we reuse the event we already have...
m_fEmptyQueueAndSleep = TRUE;
#ifdef DEBUG
AssertForNoOneWaiting(); #endif
IWantToKnowWhenCurTaskDone(); // tell the cur task to go away.....
TraceMsg(TF_SCHEDULER, "(%x)csts.dtor: call _KillScheduler", GetCurrentThreadId()); _KillScheduler( m_dwStatus == ITSSFLAG_KILL_ON_DESTROY );
// free the thread. At this point there is always
LeaveCriticalSection( &m_csListLock );
TraceMsg(TF_SCHEDULER, "csts.dtor: call u.WFSMT(m_hCurTaskEnded=%x)", m_hCurTaskEnded);
DWORD dwRes = SHWaitForSendMessageThread(m_hCurTaskEnded, INFINITE); ASSERT(dwRes == WAIT_OBJECT_0); TraceMsg(TF_SCHEDULER, "csts.dtor: u.WFSMT() done");
ASSERT( !m_pWorkerThread ); } else { LeaveCriticalSection( &m_csListLock ); }
// empty the list incase it is not empty (it should be)
DPA_EnumCallback( m_hTaskList, ListDestroyCallback, this ); DPA_DeleteAllPtrs( m_hTaskList );
DPA_Destroy( m_hTaskList ); m_hTaskList = NULL; }
if ( m_hCurTaskEnded ) CloseHandle( m_hCurTaskEnded );
DeleteCriticalSection( &m_csListLock );
DllRelease();
}
STDMETHODIMP CShellTaskScheduler::AddTask( IRunnableTask * pTask, REFTASKOWNERID rtoid, DWORD_PTR dwLParam, DWORD dwPriority ) { return AddTask2(pTask, rtoid, dwLParam, dwPriority, ITSSFLAG_TASK_PLACEINBACK); }
STDMETHODIMP CShellTaskScheduler::AddTask2( IRunnableTask * pTask, REFTASKOWNERID rtoid, DWORD_PTR dwLParam, DWORD dwPriority, DWORD grfFlags ) { if ( !pTask ) return E_INVALIDARG;
HRESULT hr = E_OUTOFMEMORY; // assume failure
TaskNode * pNewNode = new TaskNode; if ( pNewNode ) { pNewNode->pTask = pTask; pTask->AddRef(); pNewNode->toid = rtoid; pNewNode->dwPriority = dwPriority; pNewNode->dwLParam = dwLParam; pNewNode->fSuspended = FALSE;
EnterCriticalSection( &m_csListLock );
int iPos = -1;
if (grfFlags & ITSSFLAG_TASK_PLACEINFRONT) { iPos = InsertInPriorityOrder( m_hTaskList, pNewNode, TRUE ); } else if (grfFlags & ITSSFLAG_TASK_PLACEINBACK) { iPos = InsertInPriorityOrder( m_hTaskList, pNewNode, FALSE ); }
if ( iPos != -1 && m_pRunning ) { if ( m_pRunning->dwPriority < dwPriority ) { // try to suspend the current task. If this works, the task will
// return to the scheduler with E_PENDING. It will then be added
// suspended in the queue to be Resumed later....
m_pRunning->pTask->Suspend(); } }
BOOL bRes = FALSE;
if ( iPos != -1 ) { // get a worker thread and awaken it...
// we do this in the crit section because we need to test m_pWorkerThread and
// to save us from releasing and grabbing it again...
bRes = _WakeScheduler();
#ifdef DEBUG
if ( bRes && m_pWorkerThread ) { //
// We are putting this memory block in a linked list and it will most likely be freed
// from the background thread. Remove it from the per-thread memory list to avoid
// detecting it as a memory leak.
//
// WARNING - WARNING - WARNING:
// We cannot...
// assume that when pTask is Released it will be deleted, so move it
// to the other thread's memory list.
//
// This will be incorrect some of the time and we don't want to investigate
// fake leaks. -BryanSt
//transfer_to_thread_memlist( m_pWorkerThread->dwThreadID, pNewNode->pTask );
} #endif
} LeaveCriticalSection( &m_csListLock );
// we failed to add it to the list
if ( iPos == -1 ) { // we failed to add it to the list, must have been a memory failure...
pTask->Release(); // for the AddRef above
delete pNewNode; goto Leave; }
hr = bRes ? NOERROR : E_FAIL; } Leave: return hr; }
STDMETHODIMP CShellTaskScheduler::RemoveTasks( REFTASKOWNERID rtoid, DWORD_PTR dwLParam, BOOL fWaitIfRunning ) { BOOL fRemoveAll = IsEqualGUID( TOID_NULL, rtoid ); BOOL fAllItems = (dwLParam == ITSAT_DEFAULT_LPARAM ); BOOL fWaitOnHandle = FALSE;
// note, this ignores the current
EnterCriticalSection( &m_csListLock );
_RemoveTasksFromList( rtoid, dwLParam );
if ( m_pRunning && ( fWaitIfRunning || m_dwStatus == ITSSFLAG_KILL_ON_DESTROY )) { // kill the current task ...
if (( fRemoveAll || IsEqualGUID( rtoid, m_pRunning->toid )) && ( fAllItems || dwLParam == m_pRunning->dwLParam )) { ASSERT( m_pRunning->pTask ); if ( m_dwStatus == ITSSFLAG_KILL_ON_DESTROY ) { m_pRunning->pTask->Kill( fWaitIfRunning ); }
// definitive support for waiting until they are done...
// (note, only do it is there is a task running, otherwise we'll sit
// on a handle that will never fire)
if ( fWaitIfRunning ) { IWantToKnowWhenCurTaskDone();
// don't use this directly outside of the cirtical section because it can change...
ASSERT ( m_iSignalCurTask );
fWaitOnHandle = TRUE; m_iGoToSleep++; } } }
LeaveCriticalSection( &m_csListLock );
// now wait if we need to......
if ( fWaitOnHandle ) { DWORD dwRes = SHWaitForSendMessageThread(m_hCurTaskEnded, INFINITE); ASSERT(dwRes == WAIT_OBJECT_0);
EnterCriticalSection( &m_csListLock );
// Remove tasks that might have been added while the last task was finishing
_RemoveTasksFromList( rtoid, dwLParam );
m_iGoToSleep--; // See if we need to wake the thread now.
if ( m_iGoToSleep == 0 && DPA_GetPtrCount( m_hTaskList ) > 0 ) _WakeScheduler();
LeaveCriticalSection( &m_csListLock ); }
return NOERROR; }
BOOL CShellTaskScheduler::_RemoveTasksFromList( REFTASKOWNERID rtoid, DWORD_PTR dwLParam ) { // assumes that we are already holding the critical section
BOOL fRemoveAll = IsEqualGUID( TOID_NULL, rtoid ); BOOL fAllItems = (dwLParam == ITSAT_DEFAULT_LPARAM ); int iIndex = 0;
do { TaskNode * pNode = (TaskNode *) DPA_GetPtr( m_hTaskList, iIndex ); if ( !pNode ) { break; }
ASSERT( pNode ); ASSERT( pNode->pTask );
if (( fRemoveAll || IsEqualGUID( pNode->toid, rtoid )) && ( fAllItems || dwLParam == pNode->dwLParam )) { // remove it
DPA_DeletePtr( m_hTaskList, iIndex );
if ( pNode->fSuspended ) { // kill it just incase....
pNode->pTask->Kill( FALSE ); } pNode->pTask->Release(); delete pNode; } else { iIndex ++; } } while ( TRUE ); return TRUE; }
//
// CShellTaskScheduler::MoveTask
//
STDMETHODIMP CShellTaskScheduler::MoveTask( REFTASKOWNERID rtoid, DWORD_PTR dwLParam, DWORD dwPriority, DWORD grfFlags ) { int iInsert; int iIndex; BOOL fMoveAll = IsEqualGUID( TOID_NULL, rtoid ); BOOL fAllItems = (dwLParam == ITSAT_DEFAULT_LPARAM ); BOOL bMatch = FALSE ; int iIndexStart; int iIndexInc;
EnterCriticalSection( &m_csListLock );
// Init direction of search
if (grfFlags & ITSSFLAG_TASK_PLACEINFRONT) { iIndexStart = 0; iInsert = DPA_GetPtrCount( m_hTaskList ); iIndexInc = 1; } else if (grfFlags & ITSSFLAG_TASK_PLACEINBACK) { iIndexStart = iInsert = DPA_GetPtrCount( m_hTaskList ); iIndexInc = -1; }
// Find insert point (based on priority)
iIndex = 0; do { TaskNode * pNode = (TaskNode *) DPA_GetPtr( m_hTaskList, iIndex ); if ( !pNode ) { break; }
if (grfFlags & ITSSFLAG_TASK_PLACEINFRONT) { if (pNode->dwPriority <= dwPriority) { iInsert = iIndex; break; } } else if (grfFlags & ITSSFLAG_TASK_PLACEINBACK) { if (pNode->dwPriority > dwPriority) { iInsert = iIndex; } else { break; } }
iIndex++; } while (TRUE);
// Now try and locate any items.
iIndex = iIndexStart; do { TaskNode * pNode = (TaskNode *) DPA_GetPtr( m_hTaskList, iIndex ); if ( !pNode ) { break; }
if (( fMoveAll || IsEqualGUID( pNode->toid, rtoid )) && ( fAllItems || dwLParam == pNode->dwLParam )) { bMatch = TRUE;
// Can we move this node?
if ( iIndex != iInsert ) { int iPos = DPA_InsertPtr( m_hTaskList, iInsert, pNode ); if (iPos != -1) { if ( iIndex > iInsert ) { DPA_DeletePtr( m_hTaskList, iIndex + 1); // Will have shifted one
} else { DPA_DeletePtr( m_hTaskList, iIndex); } } } } iIndex += iIndexInc; } while ( !bMatch ); LeaveCriticalSection( &m_csListLock );
return (bMatch ? S_OK : S_FALSE); }
BOOL CShellTaskScheduler::_WakeScheduler( ) { // assume we are in the object's critsection.....
if ( NULL == m_pWorkerThread ) { // we need a worker quick ....
m_pWorkerThread = FetchWorker(); }
return ( NULL != m_pWorkerThread ); }
VOID CShellTaskScheduler::_KillScheduler( BOOL bKillCurTask ) { // assumes that we are already holding the critical section
if ( m_pRunning != NULL && bKillCurTask ) { ASSERT( m_pRunning->pTask );
// tell the currently running task that it should die
// quickly, because we are a separate thread than the
// one that is running the task, it can be notified
m_pRunning->pTask->Kill( FALSE ); } }
UINT CShellTaskScheduler_ThreadProc( LPVOID pParam ) { // make sure we have a message QUEUE // BOGUS - why do we need this?
MSG msg; PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE );
ASSERT( pParam );
HRESULT hrInit = SHCoInitialize();
CShellTaskScheduler::WorkerData * pWorker = (CShellTaskScheduler::WorkerData *) pParam; DWORD dwRes = 0;
TraceMsg(TF_SCHEDULER, "(?%x)ShellTaskScheduler::Thread Started", GetCurrentThreadId());
#ifdef DEBUG
pWorker->dwThreadID = GetCurrentThreadId(); #endif
// figure out who we are attatched to (where the queue is we get tasks from)
CShellTaskScheduler * pThis = pWorker->pThis;
// we must always have a valid parent object at this point....
ASSERT( pThis && IS_VALID_WRITE_PTR( pThis, CShellTaskScheduler ));
do { MSG msg; HRESULT hr = NOERROR; TaskNode * pTask = NULL;
OBJECT_ENTER_CRITICAL_SECTION( pThis, m_csListLock );
// this means we are being told to quit...
if ( pThis->m_fEmptyQueueAndSleep ) { // we are being told to empty the queue .....
DPA_EnumCallback( pThis->m_hTaskList, ListDestroyCallback, pThis ); DPA_DeleteAllPtrs( pThis->m_hTaskList ); } else if ( !pThis->m_iGoToSleep ) { // get the first item...
pTask = (TaskNode *) DPA_GetPtr( pThis->m_hTaskList, 0 ); }
if ( pTask ) { // remove from the list...
DPA_DeletePtr( pThis->m_hTaskList, 0 ); } pThis->m_pRunning = pTask;
OBJECT_LEAVE_CRITICAL_SECTION( pThis, m_csListLock );
if ( pTask == NULL ) { // cache the scheduler pointer, as we need it to leave the crit section
CShellTaskScheduler * pScheduler = pThis;
// queue is empty, go back on the thread pool.....
// we are about to enter a deep deep sleep/coma, so remove us from the object....
OBJECT_ENTER_CRITICAL_SECTION( pScheduler, m_csListLock );
HANDLE hSleep = pThis->m_fEmptyQueueAndSleep ? pThis->m_hCurTaskEnded : NULL; BOOL fEmptyAndLeave = pThis->m_fEmptyQueueAndSleep;
// make sure they didn't just add something to the queue, or have we been asked to go to sleep
if ( pThis->m_iGoToSleep || DPA_GetPtrCount( pThis->m_hTaskList ) == 0) { if ( CShellTaskScheduler::ReleaseWorker( pWorker )) { pThis = NULL; } } OBJECT_LEAVE_CRITICAL_SECTION( pScheduler, m_csListLock );
if ( pThis && !fEmptyAndLeave ) { // they must have added something at the last moment...
continue; }
// we are being emptied, tell them we are no longer attatched....
if ( hSleep ) { ReleaseSemaphore( hSleep, 1, NULL); }
break; } else { #ifndef DEBUG
//__try
{ #endif
if ( pTask->fSuspended ) { pTask->fSuspended = FALSE; hr = pTask->pTask->Resume(); } else { // run the task...
hr = pTask->pTask->Run( ); } #ifndef DEBUG
} //__except( EXCEPTION_EXECUTE_HANDLER )
// {
// ignore it.... and pray we are fine...
//}
// __endexcept
#endif
BOOL fEmptyQueue; OBJECT_ENTER_CRITICAL_SECTION( pThis, m_csListLock ); { pThis->m_pRunning = NULL;
// check to see if we have been asked to notify them
// on completion....
// NOTE: the NOT clause is needed so that we release ourselves
// NOTE: and signal them at the right point, if we do it here,
// NOTE: they leave us stranded, delete the crit section and
// NOTE: we fault.
if ( pThis->m_iSignalCurTask && !pThis->m_fEmptyQueueAndSleep ) { LONG lPrevCount = 0;
// release all those that are waiting. (we are using a semaphore
// because we are a free threaded object and God knows how many
// threads are waiting, and he passed on the information in the
// iSignalCurTask variable
ReleaseSemaphore( pThis->m_hCurTaskEnded, pThis->m_iSignalCurTask, &lPrevCount );
// reset the count.
pThis->m_iSignalCurTask = 0; } fEmptyQueue = pThis->m_fEmptyQueueAndSleep; } OBJECT_LEAVE_CRITICAL_SECTION( pThis, m_csListLock );
if ( hr != E_PENDING || fEmptyQueue ) { ULONG cRef = pTask->pTask->Release(); delete pTask; pTask = NULL; }
// empty the message queue...
while( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE )) { { #ifdef DEBUG
if (msg.message == WM_ENDSESSION) TraceMsg(TF_SCHEDULER, "(?%x)csts.tp: peek #2 got WM_ENDESSION", GetCurrentThreadId()); #endif
TranslateMessage( &msg ); DispatchMessage( &msg ); } }
ASSERT( pThis && IS_VALID_WRITE_PTR( pThis, CShellTaskScheduler ));
// the task must have been suspended because a higher priority
// task has been added to the queue..... (this only works if the
// task supports the Suspend() method).
if ( hr == E_PENDING && pTask && !fEmptyQueue ) { // put the task on the Suspended Queue ....
pTask->fSuspended = TRUE; OBJECT_ENTER_CRITICAL_SECTION( pThis, m_csListLock ); int iIndex = InsertInPriorityOrder( pThis->m_hTaskList, pTask, TRUE ); OBJECT_LEAVE_CRITICAL_SECTION( pThis, m_csListLock );
if ( iIndex == -1 ) { // we are so low on memory, kill it...
pTask->pTask->Kill( FALSE ); pTask->pTask->Release(); delete pTask; } pTask = NULL; } } } while ( TRUE );
TraceMsg(TF_SCHEDULER, "(?%x)ShellTaskScheduler::Thread Ended", GetCurrentThreadId()); SHCoUninitialize(hrInit);
return 0; }
STDMETHODIMP CShellTaskScheduler::Status( DWORD dwStatus, DWORD dwThreadTimeout ) { m_dwStatus = dwStatus & ITSSFLAG_FLAGS_MASK; if ( dwThreadTimeout != ITSS_THREAD_TIMEOUT_NO_CHANGE ) { /*
* We don't support thread termination or pool timeout any more
if ( dwStatus & ITSSFLAG_THREAD_TERMINATE_TIMEOUT ) { m_dwThreadRlsKillTimeout = dwThreadTimeout; } else if ( dwStatus & ITSSFLAG_THREAD_POOL_TIMEOUT ) { CShellTaskScheduler::s_dwComaTimeout = dwThreadTimeout; } */ } return NOERROR; }
STDMETHODIMP_(UINT) CShellTaskScheduler::CountTasks(REFTASKOWNERID rtoid) { UINT iMatch = 0; BOOL fMatchAll = IsEqualGUID( TOID_NULL, rtoid );
ENTER_CRITICAL_SECTION( m_csListLock ); if ( fMatchAll ) { iMatch = DPA_GetPtrCount( m_hTaskList ); } else { int iIndex = 0; do { TaskNode * pNode = (TaskNode * )DPA_GetPtr( m_hTaskList, iIndex ++ ); if ( !pNode ) { break; }
if ( IsEqualGUID( pNode->toid, rtoid )) { iMatch ++; } } while ( TRUE ); }
if ( m_pRunning ) { if ( fMatchAll || IsEqualGUID( rtoid, m_pRunning->toid )) { iMatch ++; } }
LEAVE_CRITICAL_SECTION( m_csListLock );
return iMatch;
}
////////////////////////////////////////////////////////////////////////////////////
int InsertInPriorityOrder( HDPA hTaskList, TaskNode * pNewNode, BOOL fStart ) { // routine assumes that we are thread safe, therfore grab the crit-section
// prior to calling this function
int iPos = -1; int iIndex = 0; do { TaskNode * pNode = (TaskNode *) DPA_GetPtr( hTaskList, iIndex ); if ( !pNode ) { break; }
// the fStart allows us to either add it before other tasks of the same
// priority or after.
if ((( pNode->dwPriority < pNewNode->dwPriority ) && !fStart ) || (( pNode->dwPriority <= pNewNode->dwPriority ) && fStart )) { iPos = DPA_InsertPtr( hTaskList, iIndex, pNewNode ); break; } iIndex ++; } while ( TRUE );
if ( iPos == -1 ) { // add item to end of list...
iPos = DPA_AppendPtr( hTaskList, pNewNode ); }
return iPos; }
CShellTaskScheduler::WorkerData * CShellTaskScheduler::FetchWorker() { WorkerData * pWorker = new WorkerData;
if ( pWorker ) { // call to Shlwapi thread pool
if ( pWorker->Init(this) && SHQueueUserWorkItem( (LPTHREAD_START_ROUTINE)CShellTaskScheduler_ThreadProc, pWorker, 0, (DWORD_PTR)NULL, (DWORD_PTR *)NULL, "browseui.dll", TPS_LONGEXECTIME | TPS_DEMANDTHREAD ) ) { return pWorker; } else delete pWorker; }
return NULL; }
// used by main thread proc to release its link the the task scheduler because it
// has run out of things to do....
BOOL CShellTaskScheduler::ReleaseWorker( WorkerData * pWorker ) { ASSERT( pWorker && IS_VALID_WRITE_PTR( pWorker, WorkerData ));
CShellTaskScheduler * pThis = pWorker->pThis;
OBJECT_ASSERT_CRITICAL_SECTION( pThis, m_csListLock );
ASSERT( pWorker && IS_VALID_WRITE_PTR( pWorker, CShellTaskScheduler::WorkerData ));
if ( DPA_GetPtrCount( pThis->m_hTaskList ) > 0 ) { // something was added to the queue at the last minute....
return FALSE; }
// we assume we have entered the critsection of pThis
pThis->m_pWorkerThread = NULL; pWorker->pThis = NULL;
delete pWorker;
return TRUE; }
///////////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CShellTaskScheduler::WorkerData::Init(CShellTaskScheduler *pts) { pThis = pts;
return TRUE; }
|