Fix for deferredDelete() bug when calling the glib loop directly

This patch makes sure that all events posted using Qt on top of the
GLib event loop have the loopLevel counter incremented.
This is done since Qt depends on the fact that all deleteLater() calls
are issued within the scope of some signal handler (in other words,
triggered by the chain sendEvent() -> notifyInternal2()).
There is a side effect though: in the conditions affected by this
patch, that is deleteLater()s issued within a glib event handler for
example, manually calling processEvents() or sendPostedEvents() with
or without the QEvent::DeferredDelete flag has the same effect, and
deferred deleted events are always processed.
While this is not a currently working feature which the patch breaks,
this side effect seems to be difficult to avoid without separating
sendPostedEvents() and processEvents() into a public and a private
method, in order to detect when they are manually called.
Such change could perhaps be done for Qt6.
An autotest for QTBUG-36434 is also included.
Autotesting for QTBUG-32859 seems to be more challenging in this
respect, due to its dependency on GLib.

Task-number: QTBUG-18434
Task-number: QTBUG-32859
Task-number: QTBUG-36434
Change-Id: Ib89175aa27c9e38bca68ae254d182b2cd21cf7e9
Reviewed-by: Olivier Goffart (Woboq GmbH) <ogoffart@woboq.com>
This commit is contained in:
Paolo Angelelli 2015-12-03 10:57:55 +01:00
parent 9daeb6fe9d
commit c5d4972577
7 changed files with 143 additions and 19 deletions

View File

@ -458,7 +458,7 @@ bool QAbstractEventDispatcher::filterNativeEvent(const QByteArray &eventType, vo
if (!d->eventFilters.isEmpty()) { if (!d->eventFilters.isEmpty()) {
// Raise the loopLevel so that deleteLater() calls in or triggered // Raise the loopLevel so that deleteLater() calls in or triggered
// by event_filter() will be processed from the main event loop. // by event_filter() will be processed from the main event loop.
QScopedLoopLevelCounter loopLevelCounter(d->threadData); QScopedScopeLevelCounter scopeLevelCounter(d->threadData);
for (int i = 0; i < d->eventFilters.size(); ++i) { for (int i = 0; i < d->eventFilters.size(); ++i) {
QAbstractNativeEventFilter *filter = d->eventFilters.at(i); QAbstractNativeEventFilter *filter = d->eventFilters.at(i);
if (!filter) if (!filter)

View File

@ -980,7 +980,7 @@ bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
// call overhead. // call overhead.
QObjectPrivate *d = receiver->d_func(); QObjectPrivate *d = receiver->d_func();
QThreadData *threadData = d->threadData; QThreadData *threadData = d->threadData;
QScopedLoopLevelCounter loopLevelCounter(threadData); QScopedScopeLevelCounter scopeLevelCounter(threadData);
if (!selfRequired) if (!selfRequired)
return doNotify(receiver, event); return doNotify(receiver, event);
return self->notify(receiver, event); return self->notify(receiver, event);
@ -1193,6 +1193,9 @@ void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags)
*/ */
void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags, int maxtime) void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags, int maxtime)
{ {
// ### Qt 6: consider splitting this method into a public and a private
// one, so that a user-invoked processEvents can be detected
// and handled properly.
QThreadData *data = QThreadData::current(); QThreadData *data = QThreadData::current();
if (!data->hasEventDispatcher()) if (!data->hasEventDispatcher())
return; return;
@ -1396,8 +1399,24 @@ void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) { if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {
// remember the current running eventloop for DeferredDelete // remember the current running eventloop for DeferredDelete
// events posted in the receiver's thread // events posted in the receiver's thread.
static_cast<QDeferredDeleteEvent *>(event)->level = data->loopLevel;
// Events sent by non-Qt event handlers (such as glib) may not
// have the scopeLevel set correctly. The scope level makes sure that
// code like this:
// foo->deleteLater();
// qApp->processEvents(); // without passing QEvent::DeferredDelete
// will not cause "foo" to be deleted before returning to the event loop.
// If the scope level is 0 while loopLevel != 0, we are called from a
// non-conformant code path, and our best guess is that the scope level
// should be 1. (Loop level 0 is special: it means that no event loops
// are running.)
int loopLevel = data->loopLevel;
int scopeLevel = data->scopeLevel;
if (scopeLevel == 0 && loopLevel != 0)
scopeLevel = 1;
static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
} }
// delete the event on exceptions to protect against memory leaks till the event is // delete the event on exceptions to protect against memory leaks till the event is
@ -1474,6 +1493,9 @@ bool QCoreApplication::compressEvent(QEvent *event, QObject *receiver, QPostEven
*/ */
void QCoreApplication::sendPostedEvents(QObject *receiver, int event_type) void QCoreApplication::sendPostedEvents(QObject *receiver, int event_type)
{ {
// ### Qt 6: consider splitting this method into a public and a private
// one, so that a user-invoked sendPostedEvents can be detected
// and handled properly.
QThreadData *data = QThreadData::current(); QThreadData *data = QThreadData::current();
QCoreApplicationPrivate::sendPostedEvents(receiver, event_type, data); QCoreApplicationPrivate::sendPostedEvents(receiver, event_type, data);
@ -1565,15 +1587,19 @@ void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type
} }
if (pe.event->type() == QEvent::DeferredDelete) { if (pe.event->type() == QEvent::DeferredDelete) {
// DeferredDelete events are only sent when we are explicitly asked to // DeferredDelete events are sent either
// (s.a. QEvent::DeferredDelete), and then only if the event loop that // 1) when the event loop that posted the event has returned; or
// posted the event has returned. // 2) if explicitly requested (with QEvent::DeferredDelete) for
int loopLevel = static_cast<QDeferredDeleteEvent *>(pe.event)->loopLevel(); // events posted by the current event loop; or
// 3) if the event was posted before the outermost event loop.
int eventLevel = static_cast<QDeferredDeleteEvent *>(pe.event)->loopLevel();
int loopLevel = data->loopLevel + data->scopeLevel;
const bool allowDeferredDelete = const bool allowDeferredDelete =
(loopLevel > data->loopLevel (eventLevel > loopLevel
|| (!loopLevel && data->loopLevel > 0) || (!eventLevel && loopLevel > 0)
|| (event_type == QEvent::DeferredDelete || (event_type == QEvent::DeferredDelete
&& loopLevel == data->loopLevel)); && eventLevel == loopLevel));
if (!allowDeferredDelete) { if (!allowDeferredDelete) {
// cannot send deferred delete // cannot send deferred delete
if (!event_type && !receiver) { if (!event_type && !receiver) {

View File

@ -56,7 +56,7 @@ QT_BEGIN_NAMESPACE
*/ */
QThreadData::QThreadData(int initialRefCount) QThreadData::QThreadData(int initialRefCount)
: _ref(initialRefCount), loopLevel(0), thread(0), threadId(0), : _ref(initialRefCount), loopLevel(0), scopeLevel(0), thread(0), threadId(0),
eventDispatcher(0), eventDispatcher(0),
quitNow(false), canWait(true), isAdopted(false), requiresCoreApplication(true) quitNow(false), canWait(true), isAdopted(false), requiresCoreApplication(true)
{ {

View File

@ -280,6 +280,7 @@ private:
public: public:
int loopLevel; int loopLevel;
int scopeLevel;
QStack<QEventLoop *> eventLoops; QStack<QEventLoop *> eventLoops;
QPostEventList postEventList; QPostEventList postEventList;
@ -295,15 +296,15 @@ public:
bool requiresCoreApplication; bool requiresCoreApplication;
}; };
class QScopedLoopLevelCounter class QScopedScopeLevelCounter
{ {
QThreadData *threadData; QThreadData *threadData;
public: public:
inline QScopedLoopLevelCounter(QThreadData *threadData) inline QScopedScopeLevelCounter(QThreadData *threadData)
: threadData(threadData) : threadData(threadData)
{ ++threadData->loopLevel; } { ++threadData->scopeLevel; }
inline ~QScopedLoopLevelCounter() inline ~QScopedScopeLevelCounter()
{ --threadData->loopLevel; } { --threadData->scopeLevel; }
}; };
// thread wrapper for the main() thread // thread wrapper for the main() thread

View File

@ -128,7 +128,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaMenuDelegate);
- (void) itemFired:(NSMenuItem*) item - (void) itemFired:(NSMenuItem*) item
{ {
QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]); QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]);
QScopedLoopLevelCounter loopLevelCounter(QGuiApplicationPrivate::instance()->threadData); QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData);
QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]];
static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated);
activatedSignal.invoke(cocoaItem, Qt::QueuedConnection); activatedSignal.invoke(cocoaItem, Qt::QueuedConnection);

View File

@ -308,7 +308,7 @@ QT_END_NAMESPACE
if ([item tag]) { if ([item tag]) {
QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]); QCocoaMenuItem *cocoaItem = reinterpret_cast<QCocoaMenuItem *>([item tag]);
QScopedLoopLevelCounter loopLevelCounter(QGuiApplicationPrivate::instance()->threadData); QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData);
cocoaItem->activated(); cocoaItem->activated();
} }
} }

View File

@ -142,6 +142,7 @@ private slots:
void qmlConnect(); void qmlConnect();
void exceptions(); void exceptions();
void noDeclarativeParentChangedOnDestruction(); void noDeclarativeParentChangedOnDestruction();
void deleteLaterInAboutToBlockHandler();
}; };
struct QObjectCreatedOnShutdown struct QObjectCreatedOnShutdown
@ -5789,6 +5790,102 @@ void tst_QObject::connectFunctorWithContext()
context->deleteLater(); context->deleteLater();
} }
class StatusChanger : public QObject
{
Q_OBJECT
public:
StatusChanger(int *status) : m_status(status)
{
}
~StatusChanger()
{
*m_status = 2;
}
private:
int *m_status;
};
class DispatcherWatcher : public QObject
{
Q_OBJECT
public:
DispatcherWatcher(QEventLoop &e, int *statusAwake, int *statusAboutToBlock) :
m_statusAboutToBlock(statusAboutToBlock),
m_statusAwake(statusAwake),
m_eventLoop(&e),
m_aboutToBlocks(0),
m_awakes(0)
{
awake = new StatusChanger(statusAwake);
abouttoblock = new StatusChanger(statusAboutToBlock);
QCOMPARE(*statusAwake, 1);
QCOMPARE(*statusAboutToBlock, 1);
connect(QAbstractEventDispatcher::instance(), SIGNAL(awake()), this, SLOT(onAwake()));
connect(QAbstractEventDispatcher::instance(), SIGNAL(aboutToBlock()), this, SLOT(onAboutToBlock()));
}
~DispatcherWatcher()
{
if (awake)
awake->deleteLater();
if (abouttoblock)
abouttoblock->deleteLater();
}
public slots:
// The order of these 2 handlers differs on different event dispatchers
void onAboutToBlock()
{
if (abouttoblock) {
abouttoblock->deleteLater();
abouttoblock = 0;
}
++m_aboutToBlocks;
}
void onAwake()
{
if (awake) {
awake->deleteLater();
awake = 0;
}
++m_awakes;
}
void onSignal1()
{
// Status check. At this point the event loop should have spinned enough to delete all the objects.
QCOMPARE(*m_statusAwake, 2);
QCOMPARE(*m_statusAboutToBlock, 2);
QMetaObject::invokeMethod(m_eventLoop, "quit", Qt::QueuedConnection);
}
private:
StatusChanger *awake;
StatusChanger *abouttoblock;
QEventLoop *m_eventLoop;
int *m_statusAwake;
int *m_statusAboutToBlock;
int m_aboutToBlocks;
int m_awakes;
};
void tst_QObject::deleteLaterInAboutToBlockHandler()
{
int statusAwake = 1;
int statusAboutToBlock = 1;
QEventLoop e;
DispatcherWatcher dw(e, &statusAwake, &statusAboutToBlock);
QTimer::singleShot(2000, &dw, &DispatcherWatcher::onSignal1);
QCOMPARE(statusAwake, 1);
QCOMPARE(statusAboutToBlock, 1);
e.exec();
QCOMPARE(statusAwake, 2);
QCOMPARE(statusAboutToBlock, 2);
}
class MyFunctor class MyFunctor
{ {
public: public: