split out generic worker thread code from ThreadedFileWorker, so memory
cards can use it, too
This commit is contained in:
@@ -44,6 +44,7 @@
|
||||
#include "RageFileDriverTimeout.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageUtil_FileDB.h"
|
||||
#include "RageUtil_WorkerThread.h"
|
||||
#include "RageLog.h"
|
||||
#include <errno.h>
|
||||
|
||||
@@ -58,21 +59,15 @@ enum ThreadRequest
|
||||
REQ_COPY,
|
||||
REQ_POPULATE_FILE_SET,
|
||||
REQ_FLUSH_DIR_CACHE,
|
||||
|
||||
REQ_SHUTDOWN,
|
||||
REQ_INVALID,
|
||||
NUM_REQUESTS,
|
||||
};
|
||||
|
||||
/* This is the class that does most of the work. */
|
||||
class ThreadedFileWorker
|
||||
class ThreadedFileWorker: public WorkerThread
|
||||
{
|
||||
public:
|
||||
ThreadedFileWorker( CString sPath );
|
||||
~ThreadedFileWorker();
|
||||
|
||||
void SetTimeout( float fSeconds );
|
||||
|
||||
/* Threaded operations. If a file operation times out, the caller loses all access
|
||||
* to the file and should fail all future operations; this is because the thread
|
||||
* is still trying to finish the operation. The thread will clean up afterwards. */
|
||||
@@ -87,29 +82,18 @@ public:
|
||||
bool FlushDirCache( const CString &sPath );
|
||||
bool PopulateFileSet( FileSet &fs, const CString &sPath );
|
||||
|
||||
protected:
|
||||
void RequestTimedOut();
|
||||
|
||||
private:
|
||||
static int StartWorkerMain( void *pThis ) { ((ThreadedFileWorker *) (pThis))->WorkerMain(); return 0; }
|
||||
void WorkerMain();
|
||||
|
||||
/* Run the given request. Return true if the operation completed, false on timeout.
|
||||
* All other return values (including error returns from the request) are returned
|
||||
* below. */
|
||||
bool DoRequest( ThreadRequest r );
|
||||
|
||||
RageThread m_WorkerThread;
|
||||
RageEvent m_WorkerEvent;
|
||||
|
||||
RageFileDriver *m_pChildDriver;
|
||||
|
||||
RageTimer m_Timeout;
|
||||
void HandleRequest( int iRequest );
|
||||
|
||||
/* All requests: */
|
||||
ThreadRequest m_Request;
|
||||
bool m_bRequestFinished;
|
||||
bool m_bTimedOut;
|
||||
RageFileDriver *m_pChildDriver;
|
||||
|
||||
/* List of files to delete: */
|
||||
vector<RageFileBasic *> m_apDeletedFiles;
|
||||
RageMutex m_DeletedFilesLock;
|
||||
|
||||
/* REQ_OPEN, REQ_POPULATE_FILE_SET, REQ_FLUSH_DIR_CACHE: */
|
||||
CString m_sRequestPath; /* in */
|
||||
@@ -154,47 +138,28 @@ void RageFileDriverTimeout::SetTimeout( float fSeconds )
|
||||
|
||||
|
||||
ThreadedFileWorker::ThreadedFileWorker( CString sPath ):
|
||||
m_WorkerEvent( "\"" + sPath + "\" worker event" )
|
||||
WorkerThread( sPath ),
|
||||
m_DeletedFilesLock( sPath + "DeletedFilesLock" )
|
||||
{
|
||||
/* Grab a reference to the child driver. We'll operate on it directly. */
|
||||
m_pChildDriver = FILEMAN->GetFileDriver( sPath );
|
||||
if( m_pChildDriver == NULL )
|
||||
LOG->Warn( "ThreadedFileWorker: Mountpoint \"%s\" not found", sPath.c_str() );
|
||||
|
||||
m_Timeout.SetZero();
|
||||
m_Request = REQ_INVALID;
|
||||
m_bTimedOut = false;
|
||||
m_pResultFile = NULL;
|
||||
m_pRequestFile = NULL;
|
||||
m_pResultBuffer = NULL;
|
||||
|
||||
m_WorkerThread.SetName( "ThreadedFileWorker fileset worker \"" + sPath + "\"" );
|
||||
m_WorkerThread.Create( StartWorkerMain, this );
|
||||
|
||||
g_apWorkersMutex.Lock();
|
||||
g_apWorkers.push_back( this );
|
||||
g_apWorkersMutex.Unlock();
|
||||
|
||||
StartThread();
|
||||
}
|
||||
|
||||
ThreadedFileWorker::~ThreadedFileWorker()
|
||||
{
|
||||
/* If we're timed out, wait. */
|
||||
m_WorkerEvent.Lock();
|
||||
if( m_bTimedOut )
|
||||
{
|
||||
LOG->Trace( "Waiting for timed-out fs \"%s\" to complete ..." );
|
||||
while( m_bTimedOut )
|
||||
m_WorkerEvent.Wait();
|
||||
}
|
||||
m_WorkerEvent.Unlock();
|
||||
|
||||
/* Disable the timeout. This will ensure that we really wait for the worker
|
||||
* thread to shut down. */
|
||||
SetTimeout( -1 );
|
||||
|
||||
/* Shut down. */
|
||||
DoRequest( REQ_SHUTDOWN );
|
||||
m_WorkerThread.Wait();
|
||||
StopThread();
|
||||
|
||||
if( m_pChildDriver != NULL )
|
||||
FILEMAN->ReleaseFileDriver( m_pChildDriver );
|
||||
@@ -212,183 +177,88 @@ ThreadedFileWorker::~ThreadedFileWorker()
|
||||
g_apWorkersMutex.Unlock();
|
||||
}
|
||||
|
||||
void ThreadedFileWorker::SetTimeout( float fSeconds )
|
||||
void ThreadedFileWorker::HandleRequest( int iRequest )
|
||||
{
|
||||
m_WorkerEvent.Lock();
|
||||
if( fSeconds == -1 )
|
||||
m_Timeout.SetZero();
|
||||
else
|
||||
{
|
||||
m_Timeout.Touch();
|
||||
m_Timeout += fSeconds;
|
||||
}
|
||||
m_WorkerEvent.Unlock();
|
||||
}
|
||||
|
||||
bool ThreadedFileWorker::DoRequest( ThreadRequest r )
|
||||
{
|
||||
ASSERT( !m_bTimedOut );
|
||||
ASSERT( m_Request == REQ_INVALID );
|
||||
|
||||
/* Set the request, and wake up the worker thread. */
|
||||
m_WorkerEvent.Lock();
|
||||
m_Request = r;
|
||||
m_WorkerEvent.Broadcast();
|
||||
|
||||
/* Wait for it to complete or time out. */
|
||||
while( !m_bRequestFinished )
|
||||
{
|
||||
bool bTimedOut = !m_WorkerEvent.Wait( &m_Timeout );
|
||||
if( bTimedOut )
|
||||
break;
|
||||
}
|
||||
|
||||
/* Don't depend on bTimedOut to tell if we timed out; only look at m_bRequestFinished,
|
||||
* to avoid a race condition where the event times out and the operation completes
|
||||
* simultaneously. */
|
||||
bool bRequestFinished = m_bRequestFinished;
|
||||
if( !m_bRequestFinished )
|
||||
{
|
||||
/* Set m_bTimedOut true. It'll be set to false when the operation actually
|
||||
* completes. */
|
||||
m_bTimedOut = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The operation completed; make sure that the worker thread doesn't erase the file. */
|
||||
m_pRequestFile = NULL;
|
||||
m_bRequestFinished = false;
|
||||
}
|
||||
m_WorkerEvent.Unlock();
|
||||
|
||||
return bRequestFinished;
|
||||
}
|
||||
|
||||
void ThreadedFileWorker::WorkerMain()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
m_WorkerEvent.Lock();
|
||||
while( m_Request == REQ_INVALID && m_apDeletedFiles.empty() )
|
||||
m_WorkerEvent.Wait();
|
||||
const ThreadRequest r = m_Request;
|
||||
m_Request = REQ_INVALID;
|
||||
|
||||
/* We're going to clean up any deleted files. Make a copy of the list; don't
|
||||
* hold the lock while we delete. */
|
||||
m_DeletedFilesLock.Lock();
|
||||
vector<RageFileBasic *> apDeletedFiles = m_apDeletedFiles;
|
||||
m_apDeletedFiles.clear();
|
||||
|
||||
m_WorkerEvent.Unlock();
|
||||
m_DeletedFilesLock.Unlock();
|
||||
|
||||
for( unsigned i = 0; i < apDeletedFiles.size(); ++i )
|
||||
delete apDeletedFiles[i];
|
||||
apDeletedFiles.clear();
|
||||
|
||||
if( r == REQ_INVALID )
|
||||
continue;
|
||||
|
||||
/* We have a request. */
|
||||
switch( r )
|
||||
{
|
||||
case REQ_OPEN:
|
||||
ASSERT( m_pResultFile == NULL );
|
||||
ASSERT( !m_sRequestPath.empty() );
|
||||
m_iResultRequest = 0;
|
||||
m_pResultFile = m_pChildDriver->Open( m_sRequestPath, m_iRequestMode, m_iResultRequest );
|
||||
break;
|
||||
|
||||
case REQ_CLOSE:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
m_apDeletedFiles.push_back( m_pRequestFile );
|
||||
break;
|
||||
|
||||
case REQ_GET_FILE_SIZE:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
m_iResultRequest = m_pRequestFile->GetFileSize();
|
||||
break;
|
||||
|
||||
case REQ_READ:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
ASSERT( m_pResultBuffer != NULL );
|
||||
m_iResultRequest = m_pRequestFile->Read( m_pResultBuffer, m_iRequestSize );
|
||||
m_sResultError = m_pRequestFile->GetError();
|
||||
break;
|
||||
|
||||
case REQ_WRITE:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
ASSERT( m_pRequestBuffer != NULL );
|
||||
m_iResultRequest = m_pRequestFile->Write( m_pRequestBuffer, m_iRequestSize );
|
||||
m_sResultError = m_pRequestFile->GetError();
|
||||
break;
|
||||
|
||||
case REQ_FLUSH:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
m_iResultRequest = m_pRequestFile->Flush();
|
||||
m_sResultError = m_pRequestFile->GetError();
|
||||
break;
|
||||
|
||||
case REQ_COPY:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
m_pResultFile = m_pRequestFile->Copy();
|
||||
break;
|
||||
|
||||
case REQ_POPULATE_FILE_SET:
|
||||
ASSERT( !m_bRequestFinished );
|
||||
ASSERT( !m_sRequestPath.empty() );
|
||||
|
||||
m_ResultFileSet = FileSet();
|
||||
m_pChildDriver->FDB->GetFileSetCopy( m_sRequestPath, m_ResultFileSet );
|
||||
break;
|
||||
|
||||
case REQ_FLUSH_DIR_CACHE:
|
||||
m_pChildDriver->FlushDirCache( m_sRequestPath );
|
||||
break;
|
||||
|
||||
case REQ_SHUTDOWN:
|
||||
break;
|
||||
|
||||
default:
|
||||
FAIL_M( ssprintf("%i", r) );
|
||||
}
|
||||
|
||||
m_WorkerEvent.Lock();
|
||||
|
||||
if( m_bTimedOut )
|
||||
{
|
||||
/* The event timed out. Clean up any residue from the last action. */
|
||||
if( m_pRequestFile != NULL )
|
||||
{
|
||||
m_apDeletedFiles.push_back( m_pResultFile );
|
||||
m_pResultFile = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if( m_pResultFile != NULL )
|
||||
{
|
||||
m_apDeletedFiles.push_back( m_pResultFile );
|
||||
m_pResultFile = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
delete m_pRequestBuffer;
|
||||
m_pRequestBuffer = NULL;
|
||||
delete m_pResultBuffer;
|
||||
m_pResultBuffer = NULL;
|
||||
|
||||
/* Clear the time-out flag, indicating that we can work again. */
|
||||
m_bTimedOut = false;
|
||||
}
|
||||
else
|
||||
m_bRequestFinished = true;
|
||||
|
||||
/* We're finished. Wake up the requester (if he's still around). */
|
||||
m_WorkerEvent.Broadcast();
|
||||
m_WorkerEvent.Unlock();
|
||||
|
||||
if( r == REQ_SHUTDOWN )
|
||||
break;
|
||||
}
|
||||
|
||||
/* We have a request. */
|
||||
switch( iRequest )
|
||||
{
|
||||
case REQ_OPEN:
|
||||
ASSERT( m_pResultFile == NULL );
|
||||
ASSERT( !m_sRequestPath.empty() );
|
||||
m_iResultRequest = 0;
|
||||
m_pResultFile = m_pChildDriver->Open( m_sRequestPath, m_iRequestMode, m_iResultRequest );
|
||||
break;
|
||||
|
||||
case REQ_CLOSE:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
delete m_pRequestFile;
|
||||
m_pRequestFile = NULL;
|
||||
|
||||
break;
|
||||
|
||||
case REQ_GET_FILE_SIZE:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
m_iResultRequest = m_pRequestFile->GetFileSize();
|
||||
break;
|
||||
|
||||
case REQ_READ:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
ASSERT( m_pResultBuffer != NULL );
|
||||
m_iResultRequest = m_pRequestFile->Read( m_pResultBuffer, m_iRequestSize );
|
||||
m_sResultError = m_pRequestFile->GetError();
|
||||
break;
|
||||
|
||||
case REQ_WRITE:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
ASSERT( m_pRequestBuffer != NULL );
|
||||
m_iResultRequest = m_pRequestFile->Write( m_pRequestBuffer, m_iRequestSize );
|
||||
m_sResultError = m_pRequestFile->GetError();
|
||||
break;
|
||||
|
||||
case REQ_FLUSH:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
m_iResultRequest = m_pRequestFile->Flush();
|
||||
m_sResultError = m_pRequestFile->GetError();
|
||||
break;
|
||||
|
||||
case REQ_COPY:
|
||||
ASSERT( m_pRequestFile != NULL );
|
||||
m_pResultFile = m_pRequestFile->Copy();
|
||||
break;
|
||||
|
||||
case REQ_POPULATE_FILE_SET:
|
||||
ASSERT( !m_sRequestPath.empty() );
|
||||
|
||||
m_ResultFileSet = FileSet();
|
||||
m_pChildDriver->FDB->GetFileSetCopy( m_sRequestPath, m_ResultFileSet );
|
||||
break;
|
||||
|
||||
case REQ_FLUSH_DIR_CACHE:
|
||||
m_pChildDriver->FlushDirCache( m_sRequestPath );
|
||||
break;
|
||||
|
||||
default:
|
||||
FAIL_M( ssprintf("%i", iRequest) );
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadedFileWorker::RequestTimedOut()
|
||||
{
|
||||
/* The event timed out. Clean up any residue from the last action. */
|
||||
SAFE_DELETE( m_pRequestFile );
|
||||
SAFE_DELETE( m_pResultFile );
|
||||
SAFE_DELETE( m_pRequestBuffer );
|
||||
SAFE_DELETE( m_pResultBuffer );
|
||||
}
|
||||
|
||||
RageFileBasic *ThreadedFileWorker::Open( const CString &sPath, int iMode, int &iErr )
|
||||
@@ -400,7 +270,7 @@ RageFileBasic *ThreadedFileWorker::Open( const CString &sPath, int iMode, int &i
|
||||
}
|
||||
|
||||
/* If we're currently in a timed-out state, fail. */
|
||||
if( m_bTimedOut )
|
||||
if( IsTimedOut() )
|
||||
{
|
||||
iErr = EFAULT; /* Win32 has no ETIMEDOUT */
|
||||
return NULL;
|
||||
@@ -412,7 +282,8 @@ RageFileBasic *ThreadedFileWorker::Open( const CString &sPath, int iMode, int &i
|
||||
if( !DoRequest(REQ_OPEN) )
|
||||
{
|
||||
LOG->Trace( "Open(%s) timed out", sPath.c_str() );
|
||||
return false;
|
||||
iErr = EFAULT; /* Win32 has no ETIMEDOUT */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
iErr = m_iResultRequest;
|
||||
@@ -426,20 +297,20 @@ void ThreadedFileWorker::Close( RageFileBasic *pFile )
|
||||
{
|
||||
ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */
|
||||
|
||||
if( !m_bTimedOut )
|
||||
if( IsTimedOut() )
|
||||
{
|
||||
/* If we're not in a timed-out state, try to wait for the deletion to complete
|
||||
* before continuing. */
|
||||
m_pRequestFile = pFile;
|
||||
DoRequest( REQ_CLOSE );
|
||||
m_pRequestFile = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Delete the file when the timeout completes. */
|
||||
m_WorkerEvent.Lock();
|
||||
m_DeletedFilesLock.Lock();
|
||||
m_apDeletedFiles.push_back( pFile );
|
||||
m_WorkerEvent.Broadcast();
|
||||
m_WorkerEvent.Unlock();
|
||||
m_DeletedFilesLock.Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,7 +319,7 @@ int ThreadedFileWorker::GetFileSize( RageFileBasic *&pFile )
|
||||
ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */
|
||||
|
||||
/* If we're currently in a timed-out state, fail. */
|
||||
if( m_bTimedOut )
|
||||
if( IsTimedOut() )
|
||||
{
|
||||
this->Close( pFile );
|
||||
pFile = NULL;
|
||||
@@ -476,7 +347,7 @@ int ThreadedFileWorker::Read( RageFileBasic *&pFile, void *pBuf, int iSize, CStr
|
||||
ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */
|
||||
|
||||
/* If we're currently in a timed-out state, fail. */
|
||||
if( m_bTimedOut )
|
||||
if( IsTimedOut() )
|
||||
{
|
||||
this->Close( pFile );
|
||||
pFile = NULL;
|
||||
@@ -515,7 +386,7 @@ int ThreadedFileWorker::Write( RageFileBasic *&pFile, const void *pBuf, int iSiz
|
||||
ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */
|
||||
|
||||
/* If we're currently in a timed-out state, fail. */
|
||||
if( m_bTimedOut )
|
||||
if( IsTimedOut() )
|
||||
{
|
||||
this->Close( pFile );
|
||||
pFile = NULL;
|
||||
@@ -554,7 +425,7 @@ int ThreadedFileWorker::Flush( RageFileBasic *&pFile, CString &sError )
|
||||
ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */
|
||||
|
||||
/* If we're currently in a timed-out state, fail. */
|
||||
if( m_bTimedOut )
|
||||
if( IsTimedOut() )
|
||||
{
|
||||
this->Close( pFile );
|
||||
pFile = NULL;
|
||||
@@ -576,6 +447,8 @@ int ThreadedFileWorker::Flush( RageFileBasic *&pFile, CString &sError )
|
||||
return -1;
|
||||
}
|
||||
|
||||
m_pRequestFile = NULL;
|
||||
|
||||
return m_iResultRequest;
|
||||
}
|
||||
|
||||
@@ -584,7 +457,7 @@ RageFileBasic *ThreadedFileWorker::Copy( RageFileBasic *&pFile, CString &sError
|
||||
ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */
|
||||
|
||||
/* If we're currently in a timed-out state, fail. */
|
||||
if( m_bTimedOut )
|
||||
if( IsTimedOut() )
|
||||
{
|
||||
this->Close( pFile );
|
||||
pFile = NULL;
|
||||
@@ -606,6 +479,7 @@ RageFileBasic *ThreadedFileWorker::Copy( RageFileBasic *&pFile, CString &sError
|
||||
}
|
||||
|
||||
RageFileBasic *pRet = m_pResultFile;
|
||||
m_pRequestFile = NULL;
|
||||
m_pResultFile = NULL;
|
||||
|
||||
return pRet;
|
||||
@@ -618,12 +492,11 @@ bool ThreadedFileWorker::PopulateFileSet( FileSet &fs, const CString &sPath )
|
||||
return false;
|
||||
|
||||
/* If we're currently in a timed-out state, fail. */
|
||||
if( m_bTimedOut )
|
||||
if( IsTimedOut() )
|
||||
return false;
|
||||
|
||||
/* Fill in a different FileSet; copy the results in on success. */
|
||||
m_sRequestPath = sPath;
|
||||
m_bRequestFinished = false;
|
||||
|
||||
/* Kick off the worker thread, and wait for it to finish. */
|
||||
if( !DoRequest(REQ_POPULATE_FILE_SET) )
|
||||
@@ -642,7 +515,7 @@ bool ThreadedFileWorker::FlushDirCache( const CString &sPath )
|
||||
return false;
|
||||
|
||||
/* If we're currently in a timed-out state, fail. */
|
||||
if( m_bTimedOut )
|
||||
if( IsTimedOut() )
|
||||
return false;
|
||||
|
||||
m_sRequestPath = sPath;
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
#include "global.h"
|
||||
#include "RageUtil_WorkerThread.h"
|
||||
#include "RageLog.h"
|
||||
|
||||
WorkerThread::WorkerThread( const CString &sName ):
|
||||
m_WorkerEvent( "\"" + sName + "\" worker event" )
|
||||
{
|
||||
m_Timeout.SetZero();
|
||||
m_iRequest = REQ_NONE;
|
||||
m_bTimedOut = false;
|
||||
m_fHeartbeat = -1;
|
||||
m_bRequestFinished = false;
|
||||
|
||||
m_WorkerThread.SetName( "ThreadedFileWorker fileset worker \"" + sName + "\"" );
|
||||
}
|
||||
|
||||
WorkerThread::~WorkerThread()
|
||||
{
|
||||
/* The worker thread must be stopped by the derived class. */
|
||||
ASSERT( !m_WorkerThread.IsCreated() );
|
||||
}
|
||||
|
||||
void WorkerThread::SetTimeout( float fSeconds )
|
||||
{
|
||||
m_WorkerEvent.Lock();
|
||||
if( fSeconds == -1 )
|
||||
m_Timeout.SetZero();
|
||||
else
|
||||
{
|
||||
m_Timeout.Touch();
|
||||
m_Timeout += fSeconds;
|
||||
}
|
||||
m_WorkerEvent.Unlock();
|
||||
}
|
||||
|
||||
|
||||
void WorkerThread::StartThread()
|
||||
{
|
||||
ASSERT( !m_WorkerThread.IsCreated() );
|
||||
|
||||
m_WorkerThread.Create( StartWorkerMain, this );
|
||||
}
|
||||
|
||||
void WorkerThread::StopThread()
|
||||
{
|
||||
/* If we're timed out, wait. */
|
||||
m_WorkerEvent.Lock();
|
||||
if( m_bTimedOut )
|
||||
{
|
||||
LOG->Trace( "Waiting for timed-out fs \"%s\" to complete ..." );
|
||||
while( m_bTimedOut )
|
||||
m_WorkerEvent.Wait();
|
||||
}
|
||||
m_WorkerEvent.Unlock();
|
||||
|
||||
/* Disable the timeout. This will ensure that we really wait for the worker
|
||||
* thread to shut down. */
|
||||
SetTimeout( -1 );
|
||||
|
||||
/* Shut down. */
|
||||
DoRequest( REQ_SHUTDOWN );
|
||||
m_WorkerThread.Wait();
|
||||
}
|
||||
|
||||
bool WorkerThread::DoRequest( int iRequest )
|
||||
{
|
||||
ASSERT( !m_bTimedOut );
|
||||
ASSERT( m_iRequest == REQ_NONE );
|
||||
|
||||
/* Set the request, and wake up the worker thread. */
|
||||
m_WorkerEvent.Lock();
|
||||
|
||||
m_iRequest = iRequest;
|
||||
m_WorkerEvent.Broadcast();
|
||||
|
||||
/* Wait for it to complete or time out. */
|
||||
while( !m_bRequestFinished )
|
||||
{
|
||||
bool bTimedOut = !m_WorkerEvent.Wait( &m_Timeout );
|
||||
if( bTimedOut )
|
||||
break;
|
||||
}
|
||||
|
||||
const bool bRequestFinished = m_bRequestFinished;
|
||||
if( m_bRequestFinished )
|
||||
{
|
||||
/* The request finished successfully. It's the calling function's
|
||||
* responsibility to clean up. */
|
||||
m_bRequestFinished = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* The request hasn't finished yet. Set m_bTimedOut true. This tells the
|
||||
* still-running request that it timed out, and so it's the thread's
|
||||
* responsibility to clean up--we can't do it, since we'd collide with the
|
||||
* request. */
|
||||
m_bTimedOut = true;
|
||||
}
|
||||
m_WorkerEvent.Unlock();
|
||||
|
||||
return bRequestFinished;
|
||||
}
|
||||
|
||||
void WorkerThread::WorkerMain()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
bool bTimeToRunHeartbeat = false;
|
||||
m_WorkerEvent.Lock();
|
||||
while( m_iRequest == REQ_NONE && !bTimeToRunHeartbeat ) //&& m_apDeletedFiles.empty() )
|
||||
{
|
||||
if( !m_WorkerEvent.Wait( m_fHeartbeat != -1? &m_NextHeartbeat:NULL ) )
|
||||
bTimeToRunHeartbeat = true;
|
||||
}
|
||||
const int iRequest = m_iRequest;
|
||||
m_iRequest = REQ_NONE;
|
||||
|
||||
m_WorkerEvent.Unlock();
|
||||
|
||||
if( iRequest == REQ_SHUTDOWN )
|
||||
break;
|
||||
|
||||
/* If it's time to run a heartbeat, do so. */
|
||||
if( bTimeToRunHeartbeat )
|
||||
{
|
||||
DoHeartbeat();
|
||||
|
||||
m_NextHeartbeat.Touch();
|
||||
m_NextHeartbeat += m_fHeartbeat;
|
||||
}
|
||||
|
||||
if( iRequest != REQ_NONE )
|
||||
{
|
||||
/* Handle the request. */
|
||||
HandleRequest( iRequest );
|
||||
|
||||
/* Lock the mutex, to keep DoRequest where it is (if it's still running). */
|
||||
/* The request is finished. If it timed out, clear the timeout flag and
|
||||
* call RequestTimedOut, to allow cleaning up. */
|
||||
m_WorkerEvent.Lock();
|
||||
|
||||
if( m_bTimedOut )
|
||||
{
|
||||
/* The calling thread timed out. It's already gone and moved on, so
|
||||
* it's our responsibility to clean up. No new requests will come in
|
||||
* until we clear m_bTimedOut, so we can safely unlock and clean up. */
|
||||
m_WorkerEvent.Unlock();
|
||||
|
||||
RequestTimedOut();
|
||||
|
||||
/* Clear the time-out flag, indicating that we can work again. */
|
||||
m_bTimedOut = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_bRequestFinished = true;
|
||||
|
||||
/* We're finished. Wake up the requester. */
|
||||
m_WorkerEvent.Broadcast();
|
||||
m_WorkerEvent.Unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2005 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,87 @@
|
||||
/* WorkerThread - a worker thread for operations that are allowed to time out. */
|
||||
|
||||
#ifndef RAGE_UTIL_WORKER_THREAD_H
|
||||
#define RAGE_UTIL_WORKER_THREAD_H
|
||||
|
||||
#include "RageThreads.h"
|
||||
#include "RageTimer.h"
|
||||
|
||||
class WorkerThread
|
||||
{
|
||||
public:
|
||||
WorkerThread( const CString &sName );
|
||||
virtual ~WorkerThread();
|
||||
|
||||
/* Call SetTimeout(10) to start a timeout period of 10 seconds. This is not a
|
||||
* per-request timeout; you have 10 seconds to do your work, at which point all
|
||||
* requests time out until SetTimeout is called again. */
|
||||
void SetTimeout( float fSeconds );
|
||||
|
||||
protected:
|
||||
/* Call this in the derived class to start and stop the thread. */
|
||||
void StartThread();
|
||||
void StopThread();
|
||||
|
||||
/* Run the given request. Return true if the operation completed, false on timeout.
|
||||
* Always call IsTimedOut() first; if true is returned, the thread is currently
|
||||
* timed out and DoRequest() must not be called. */
|
||||
bool DoRequest( int iRequest );
|
||||
|
||||
/* Return true if the last operation has timed out and has not yet recovered. */
|
||||
bool IsTimedOut() const { return m_bTimedOut; }
|
||||
|
||||
/* Overload this in the derived class to handle requests. */
|
||||
virtual void HandleRequest( int iRequest ) = 0;
|
||||
|
||||
/* If DoRequest times out, this will be called in the thread after completion.
|
||||
* Clean up. No new requests will be allowed until this completes. */
|
||||
virtual void RequestTimedOut() { }
|
||||
|
||||
/* Enable a heartbeat. DoHeartbeat will be called every fSeconds while idle.
|
||||
* DoHeartbeat may safely time out; if DoRequest tries to start a request in
|
||||
* the main thread, it'll simply time out. */
|
||||
void SetHeartbeat( float fSeconds ) { m_fHeartbeat = fSeconds; m_NextHeartbeat.Touch(); }
|
||||
virtual void DoHeartbeat() { }
|
||||
|
||||
private:
|
||||
static int StartWorkerMain( void *pThis ) { ((WorkerThread *) (pThis))->WorkerMain(); return 0; }
|
||||
void WorkerMain();
|
||||
|
||||
enum { REQ_SHUTDOWN = -1, REQ_NONE = -2 };
|
||||
RageThread m_WorkerThread;
|
||||
RageEvent m_WorkerEvent;
|
||||
int m_iRequest;
|
||||
bool m_bRequestFinished;
|
||||
bool m_bTimedOut;
|
||||
RageTimer m_Timeout;
|
||||
|
||||
float m_fHeartbeat;
|
||||
RageTimer m_NextHeartbeat;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2005 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -2162,6 +2162,12 @@ cl /Zl /nologo /c verstub.cpp /Fo"$(IntDir)"\
|
||||
<File
|
||||
RelativePath="RageUtil_FileDB.h">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="RageUtil_WorkerThread.cpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath="RageUtil_WorkerThread.h">
|
||||
</File>
|
||||
<Filter
|
||||
Name="Helpers"
|
||||
Filter="">
|
||||
|
||||
Reference in New Issue
Block a user