windows-nt/Source/XPSP1/NT/shell/browseui/schedule.cpp
2020-09-26 16:20:57 +08:00

1140 lines
33 KiB
C++

#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;
}