QThread: terminate on exceptions leaving run()

Align ourselves to the Standard Library and call std::terminate if an
exception leaves the thread entry point (that is, run()).

On platforms using pthreads, thread cancellation needs to be taken in
special consideration, since it looks like it was supported before.

On Glibc, and when using C++, pthread_cancel and pthread_exit are
implemented by throwing a special kind of exception that can be caught,
but must always be rethrown. That exception is then used to activate the
cancellation clean-up handlers. (This is non-Standard C++ behavior.)

So: mimic what libstdc++'s std::thread does to support Glibc's pthread
cancellation.

At this time, it looks like libc++ has no support for this, and when
used in combination with Glibc a thread cancellation results in a crash
(also because it does not seem to terminate() when exceptions leave the
thread).

[ChangeLog][QtCore][QThread] An exception escaping from QThread::run()
will now result in immediate and abnormal program termination. The same
applies if an exception leaves a slot connected directly to the
QThread::started() or QThread::finished() signals.

Change-Id: I73cc93cf06c57018e149a578cc9d4cd0d6fc00ef
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Giuseppe D'Angelo 2016-08-07 20:58:54 +02:00
parent 164392548e
commit 5677b70eee
3 changed files with 102 additions and 59 deletions

View File

@ -184,8 +184,8 @@ public:
#endif // Q_OS_UNIX
#ifdef Q_OS_WIN
static unsigned int __stdcall start(void *);
static void finish(void *, bool lockAnyway=true);
static unsigned int __stdcall start(void *) Q_DECL_NOEXCEPT;
static void finish(void *, bool lockAnyway=true) Q_DECL_NOEXCEPT;
Qt::HANDLE handle;
unsigned int id;

View File

@ -61,6 +61,10 @@
#include "qdebug.h"
#ifdef __GLIBCXX__
#include <cxxabi.h>
#endif
#include <sched.h>
#include <errno.h>
@ -324,49 +328,70 @@ void *QThreadPrivate::start(void *arg)
#endif
pthread_cleanup_push(QThreadPrivate::finish, arg);
QThread *thr = reinterpret_cast<QThread *>(arg);
QThreadData *data = QThreadData::get2(thr);
#ifndef QT_NO_EXCEPTIONS
try
#endif
{
QMutexLocker locker(&thr->d_func()->mutex);
QThread *thr = reinterpret_cast<QThread *>(arg);
QThreadData *data = QThreadData::get2(thr);
// do we need to reset the thread priority?
if (int(thr->d_func()->priority) & ThreadPriorityResetFlag) {
thr->d_func()->setPriority(QThread::Priority(thr->d_func()->priority & ~ThreadPriorityResetFlag));
{
QMutexLocker locker(&thr->d_func()->mutex);
// do we need to reset the thread priority?
if (int(thr->d_func()->priority) & ThreadPriorityResetFlag) {
thr->d_func()->setPriority(QThread::Priority(thr->d_func()->priority & ~ThreadPriorityResetFlag));
}
data->threadId.store(to_HANDLE(pthread_self()));
set_thread_data(data);
data->ref();
data->quitNow = thr->d_func()->exited;
}
data->threadId.store(to_HANDLE(pthread_self()));
set_thread_data(data);
data->ref();
data->quitNow = thr->d_func()->exited;
}
if (data->eventDispatcher.load()) // custom event dispatcher set?
data->eventDispatcher.load()->startingUp();
else
createEventDispatcher(data);
if (data->eventDispatcher.load()) // custom event dispatcher set?
data->eventDispatcher.load()->startingUp();
else
createEventDispatcher(data);
#if (defined(Q_OS_LINUX) || defined(Q_OS_MAC) || defined(Q_OS_QNX))
{
// sets the name of the current thread.
QString objectName = thr->objectName();
{
// sets the name of the current thread.
QString objectName = thr->objectName();
pthread_t thread_id = from_HANDLE<pthread_t>(data->threadId.load());
if (Q_LIKELY(objectName.isEmpty()))
setCurrentThreadName(thread_id, thr->metaObject()->className());
else
setCurrentThreadName(thread_id, objectName.toLocal8Bit());
}
pthread_t thread_id = from_HANDLE<pthread_t>(data->threadId.load());
if (Q_LIKELY(objectName.isEmpty()))
setCurrentThreadName(thread_id, thr->metaObject()->className());
else
setCurrentThreadName(thread_id, objectName.toLocal8Bit());
}
#endif
emit thr->started(QThread::QPrivateSignal());
emit thr->started(QThread::QPrivateSignal());
#if !defined(Q_OS_ANDROID)
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_testcancel();
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_testcancel();
#endif
thr->run();
thr->run();
}
#ifndef QT_NO_EXCEPTIONS
#ifdef __GLIBCXX__
// POSIX thread cancellation under glibc is implemented by throwing an exception
// of this type. Do what libstdc++ is doing and handle it specially in order not to
// abort the application if user's code calls a cancellation function.
catch (const abi::__forced_unwind &) {
throw;
}
#endif // __GLIBCXX__
catch (...) {
qTerminate();
}
#endif // QT_NO_EXCEPTIONS
// This pop runs finish() below. It's outside the try/catch (and has its
// own try/catch) to prevent finish() to be run in case an exception is
// thrown.
pthread_cleanup_pop(1);
return 0;
@ -374,35 +399,53 @@ void *QThreadPrivate::start(void *arg)
void QThreadPrivate::finish(void *arg)
{
QThread *thr = reinterpret_cast<QThread *>(arg);
QThreadPrivate *d = thr->d_func();
#ifndef QT_NO_EXCEPTIONS
try
#endif
{
QThread *thr = reinterpret_cast<QThread *>(arg);
QThreadPrivate *d = thr->d_func();
QMutexLocker locker(&d->mutex);
QMutexLocker locker(&d->mutex);
d->isInFinish = true;
d->priority = QThread::InheritPriority;
void *data = &d->data->tls;
locker.unlock();
emit thr->finished(QThread::QPrivateSignal());
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QThreadStorageData::finish((void **)data);
locker.relock();
QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher.load();
if (eventDispatcher) {
d->data->eventDispatcher = 0;
d->isInFinish = true;
d->priority = QThread::InheritPriority;
void *data = &d->data->tls;
locker.unlock();
eventDispatcher->closingDown();
delete eventDispatcher;
emit thr->finished(QThread::QPrivateSignal());
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
QThreadStorageData::finish((void **)data);
locker.relock();
QAbstractEventDispatcher *eventDispatcher = d->data->eventDispatcher.load();
if (eventDispatcher) {
d->data->eventDispatcher = 0;
locker.unlock();
eventDispatcher->closingDown();
delete eventDispatcher;
locker.relock();
}
d->running = false;
d->finished = true;
d->interruptionRequested = false;
d->isInFinish = false;
d->thread_done.wakeAll();
}
d->running = false;
d->finished = true;
d->interruptionRequested = false;
d->isInFinish = false;
d->thread_done.wakeAll();
#ifndef QT_NO_EXCEPTIONS
#ifdef __GLIBCXX__
// POSIX thread cancellation under glibc is implemented by throwing an exception
// of this type. Do what libstdc++ is doing and handle it specially in order not to
// abort the application if user's code calls a cancellation function.
catch (const abi::__forced_unwind &) {
throw;
}
#endif // __GLIBCXX__
catch (...) {
qTerminate();
}
#endif // QT_NO_EXCEPTIONS
}

View File

@ -344,7 +344,7 @@ void QThreadPrivate::createEventDispatcher(QThreadData *data)
#ifndef QT_NO_THREAD
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg)
unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg) Q_DECL_NOEXCEPT
{
QThread *thr = reinterpret_cast<QThread *>(arg);
QThreadData *data = QThreadData::get2(thr);
@ -381,7 +381,7 @@ unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(voi
return 0;
}
void QThreadPrivate::finish(void *arg, bool lockAnyway)
void QThreadPrivate::finish(void *arg, bool lockAnyway) Q_DECL_NOEXCEPT
{
QThread *thr = reinterpret_cast<QThread *>(arg);
QThreadPrivate *d = thr->d_func();