diff --git a/doomsday/engine/portable/src/busymode.cpp b/doomsday/engine/portable/src/busymode.cpp index f1cfb98401..d833148ea9 100644 --- a/doomsday/engine/portable/src/busymode.cpp +++ b/doomsday/engine/portable/src/busymode.cpp @@ -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. @@ -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); @@ -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; diff --git a/doomsday/engine/portable/src/dd_init.cpp b/doomsday/engine/portable/src/dd_init.cpp index c2e9b09c0e..989b4b6528 100644 --- a/doomsday/engine/portable/src/dd_init.cpp +++ b/doomsday/engine/portable/src/dd_init.cpp @@ -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 } diff --git a/doomsday/libdeng/include/de/concurrency.h b/doomsday/libdeng/include/de/concurrency.h index 5cad740a0b..ea0298dc74 100644 --- a/doomsday/libdeng/include/de/concurrency.h +++ b/doomsday/libdeng/include/de/concurrency.h @@ -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 @@ -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(); @@ -58,6 +66,8 @@ protected slots: systhreadfunc_t _callback; void* _parm; int _returnValue; + systhreadexitstatus_t _exitStatus; + void (*_terminationFunc)(systhreadexitstatus_t); }; #endif // __DENG__ @@ -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. diff --git a/doomsday/libdeng/src/concurrency.cpp b/doomsday/libdeng/src/concurrency.cpp index 0ed7fd7ae7..336176cc24 100644 --- a/doomsday/libdeng/src/concurrency.cpp +++ b/doomsday/libdeng/src/concurrency.cpp @@ -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."; @@ -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(); @@ -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. @@ -121,7 +142,16 @@ 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(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(handle); assert(static_cast(t) != QThread::currentThread()); @@ -129,6 +159,11 @@ int Sys_WaitThread(thread_t handle, int 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();