Skip to content

Commit

Permalink
libdeng|Concurrency: Thread exit status
Browse files Browse the repository at this point in the history
It is now possible to query how the worker thread ended:
normally, forcibly, or because of an exception.
  • Loading branch information
skyjake committed Jul 25, 2012
1 parent 04ba10d commit 78478c7
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 8 deletions.
18 changes: 17 additions & 1 deletion doomsday/engine/portable/src/busymode.cpp
Expand Up @@ -95,6 +95,20 @@ BusyTask* BusyMode_CurrentTask(void)
return busyTask;
}

/**
* Callback that is called from the busy worker thread when it exists.
* @param status Exit status.
*/
static void busyWorkerTerminated(systhreadexitstatus_t status)
{
DENG_ASSERT(BusyMode_Active());

if(status == DENG_THREAD_STOPPED_WITH_EXCEPTION)
{
BusyMode_WorkerError("Uncaught exception from busy thread.");
}
}

/**
* Sets up module state for running a busy task. After this the busy mode event
* loop is started. The loop will run until the worker thread exits.
Expand Down Expand Up @@ -126,6 +140,7 @@ static void beginTask(BusyTask* task)
// Start the busy worker thread, which will process the task in the
// background while we keep the user occupied with nice animations.
busyThread = Sys_StartThread(busyTask->worker, busyTask->workerData);
Thread_SetCallback(busyThread, busyWorkerTerminated);

// Switch the engine loop and window to the busy mode.
LegacyCore_SetLoopFunc(BusyMode_Loop);
Expand Down Expand Up @@ -380,13 +395,14 @@ static void stopEventLoopWithValue(int result)
static void BusyMode_Exit(void)
{
int result;
systhreadexitstatus_t status;

LIBDENG_ASSERT_IN_MAIN_THREAD();

busyDone = true;

// Make sure the worker finishes before we continue.
result = Sys_WaitThread(busyThread, busyTaskEndedWithError? 100 : 5000);
result = Sys_WaitThread(busyThread, busyTaskEndedWithError? 100 : 5000, &status);
busyThread = NULL;
busyTask = NULL;

Expand Down
5 changes: 3 additions & 2 deletions doomsday/engine/portable/src/dd_init.cpp
Expand Up @@ -165,8 +165,9 @@ int main(int argc, char** argv)
// Set up the application-wide menu.
menuBar = new QMenuBar;
QMenu* gameMenu = menuBar->addMenu("&Game");
QAction* checkForUpdates = gameMenu->addAction(
"Check For &Updates...", Updater_Instance(), SLOT(checkNowShowingProgress()));
QAction* checkForUpdates =
gameMenu->addAction("Check For &Updates...",
Updater_Instance(), SLOT(checkNowShowingProgress()));
checkForUpdates->setMenuRole(QAction::ApplicationSpecificRole);
#endif
}
Expand Down
28 changes: 25 additions & 3 deletions doomsday/libdeng/include/de/concurrency.h
Expand Up @@ -32,6 +32,12 @@ typedef int (*systhreadfunc_t) (void* parm);
typedef void* mutex_t;
typedef void* sem_t;

typedef enum systhreadexitstatus_e {
DENG_THREAD_STOPPED_NORMALLY,
DENG_THREAD_STOPPED_WITH_FORCE, // terminated
DENG_THREAD_STOPPED_WITH_EXCEPTION
} systhreadexitstatus_t;

#ifdef __cplusplus

#ifdef __DENG__ // libdeng internal
Expand All @@ -50,6 +56,8 @@ class CallbackThread : public QThread

void run();
int exitValue() const;
systhreadexitstatus_t exitStatus() const;
void setTerminationFunc(void (*func)(systhreadexitstatus_t));

protected slots:
void deleteNow();
Expand All @@ -58,6 +66,8 @@ protected slots:
systhreadfunc_t _callback;
void* _parm;
int _returnValue;
systhreadexitstatus_t _exitStatus;
void (*_terminationFunc)(systhreadexitstatus_t);
};
#endif // __DENG__

Expand Down Expand Up @@ -87,16 +97,28 @@ DENG_PUBLIC thread_t Sys_StartThread(systhreadfunc_t startpos, void* parm);

DENG_PUBLIC void Thread_Sleep(int milliseconds);

/**
* Sets a callback function that is called from the worker thread right before
* it exits. The callback is given the exit status of the thread as a
* parameter.
*
* @param thread Thread handle.
* @param terminationFunc Callback to call before terminating the thread.
*/
DENG_PUBLIC void Thread_SetCallback(thread_t thread, void (*terminationFunc)(systhreadexitstatus_t));

/**
* Wait for a thread to stop. If the thread does not stop after @a timeoutMs,
* it will be forcibly terminated.
*
* @param handle Thread handle.
* @param timeoutMs How long to wait until the thread terminates.
* @param handle Thread handle.
* @param timeoutMs How long to wait until the thread terminates.
* @param exitStatus If not @c NULL, the exit status is returned here.
* @c true for normal exit, @c false if exception was caught.
*
* @return Return value of the thread.
*/
DENG_PUBLIC int Sys_WaitThread(thread_t handle, int timeoutMs);
DENG_PUBLIC int Sys_WaitThread(thread_t handle, int timeoutMs, systhreadexitstatus_t* exitStatus);

/**
* @param handle Handle to the thread to return the id of.
Expand Down
39 changes: 37 additions & 2 deletions doomsday/libdeng/src/concurrency.cpp
Expand Up @@ -33,7 +33,9 @@
static uint mainThreadId = 0; ///< ID of the main thread.

CallbackThread::CallbackThread(systhreadfunc_t func, void* param)
: _callback(func), _parm(param), _returnValue(0)
: _callback(func), _parm(param), _returnValue(0),
_exitStatus(DENG_THREAD_STOPPED_NORMALLY),
_terminationFunc(0)
{
//qDebug() << "CallbackThread:" << this << "created.";

Expand Down Expand Up @@ -65,18 +67,27 @@ void CallbackThread::deleteNow()

void CallbackThread::run()
{
_exitStatus = DENG_THREAD_STOPPED_WITH_FORCE;

try
{
if(_callback)
{
_returnValue = _callback(_parm);
}
_exitStatus = DENG_THREAD_STOPPED_NORMALLY;
}
catch(const std::exception& error)
{
LOG_AS("CallbackThread");
LOG_ERROR(QString("Uncaught exception: ") + error.what());
_returnValue = -1;
_exitStatus = DENG_THREAD_STOPPED_WITH_EXCEPTION;
}

if(_terminationFunc)
{
_terminationFunc(_exitStatus);
}

Garbage_ClearForThread();
Expand All @@ -87,6 +98,16 @@ int CallbackThread::exitValue() const
return _returnValue;
}

systhreadexitstatus_t CallbackThread::exitStatus() const
{
return _exitStatus;
}

void CallbackThread::setTerminationFunc(void (*func)(systhreadexitstatus_t))
{
_terminationFunc = func;
}

void Sys_MarkAsMainThread(void)
{
// This is the main thread.
Expand Down Expand Up @@ -121,14 +142,28 @@ void Thread_KillAbnormally(thread_t handle)
t->terminate();
}

int Sys_WaitThread(thread_t handle, int timeoutMs)
void Thread_SetCallback(thread_t thread, void (*terminationFunc)(systhreadexitstatus_t))
{
CallbackThread* t = reinterpret_cast<CallbackThread*>(thread);
DENG_ASSERT(t);
if(!t) return;

t->setTerminationFunc(terminationFunc);
}

int Sys_WaitThread(thread_t handle, int timeoutMs, systhreadexitstatus_t* exitStatus)
{
CallbackThread* t = reinterpret_cast<CallbackThread*>(handle);
assert(static_cast<QThread*>(t) != QThread::currentThread());
t->wait(timeoutMs);
if(!t->isFinished())
{
LOG_WARNING("Thread did not stop in time, forcibly killing it.");
if(exitStatus) *exitStatus = DENG_THREAD_STOPPED_WITH_FORCE;
}
else
{
if(exitStatus) *exitStatus = t->exitStatus();
}
t->deleteLater(); // get rid of it
return t->exitValue();
Expand Down

0 comments on commit 78478c7

Please sign in to comment.