Details
-
Epic
-
Resolution: Unresolved
-
P3: Somewhat important
-
None
-
dev
-
None
-
deleteLater()
-
-
13074a967 (dev), 46588fbb5 (dev), 7fac2fac9 (dev), c3a2b9f35 (dev), fd38df3ec (dev), d909a8ed6 (6.6), 2dfa02b46 (tqtc/lts-6.5), d50746ef3 (6.7)
Description
A short history of deleteLater
The feature was first introduced in 7781a333b084821f86723cc562826076cb0a235d, under the name QObject::deferredDelete(), and renamed to QObject::deleteLater() soon after. The initial implementation simply posted a QEvent::DeferredDelete event to the Qt event queue, that deleted the object when the event was received by the object.
The initial implementation failed to take into account nested event loops, and would end up processing the deferred delete event in the nested event loop, for an object that would still be referenced after the nested loop returned:
object->deleteLater();
QEventLoop loop; loop.exec();
object->doSomething(); // Crash
To fix this, a55881148abfcedb977a2918133b15ae9cfe5d31 bound each deferred delete event to the event loop it was posted from, and only allowed processing of deferred delete events matching the current run loop. This approach was partially reverted due to breaking a few things, but re-applied in another form in 83bab4ceec67f7bb091d674af24ae440bf5da5d9, with the same behavior.
The logic was further changed in 1aabbce55d42a154461c43e1107eb313771d0bbb a few years later, when an explicit event loop level counter was added. Instead of tying each deferred delete event to a specific event loop, the event was now tied to the threads’ event loop level at the time of the deleteLater() call.
The thread’s event loop level was bumped not only from QEventLoop::exec(), but also from QCoreApplication::notifyInternal(), i.e. when sending Qt events. The logic for when to allow deferred deletion was changed accordingly, to process the deferred delete if the event’s level was lower than the thread’s loop level, since the typical case was now:
- QEventLoop::exec(), level = 1
- sendPostedEvents()
- notifyInternal(), level = 2
- deleteLater(), record level = 2
- Process deferred delete event, level back to 1
- delete object
- notifyInternal(), level = 2
- sendPostedEvents()
The end result was still that we would process the deferred delete events as part of the current QEventLoop, as long as we had returned from sending the Qt event that resulted in deleteLater().
However, one thing the patch didn’t account for was that sending Qt events were not the only way to end up in user code that did deferred deletions. For example, dispatching native events, or handling window system events, would also end up in user code. To fix this a new QScopedLoopLevelCounter was introduced in eeff03224dd2ecdf8d55efe8190cbba213fa9193, and sprinkled around in various places, to ensure the loop level was raised similarly to what it was when sending Qt events from notifyInternal(). With 722cbfe7384aa5692af4c3f03b562082fadcb93c the helper class was also used to do the loop level raising in notifyInternal().
An additional tweak was made in d51aa0c4b33c2ae23347ad15b974470cf247ca39, where we would only record the loop level if the deleted object lived in the same thread as the one making the deferred delete, effectively deferring the deletion of the object until the thread finishes, via QThreadPrivate::finish().
These workarounds were unfortunately not enough, as there were still cases where we would end up in Qt code without bumping the event loop level, for example in native callbacks from the glib event loop. c5d49725779292a04fed599eb7f508d334ffc5c3 tried to tackle this once and for all, by splitting the event loop level counter into two, one for counting the QEventLoop level, and one for the deferred delete scope. It then added a catch-all fallback, where if the deferred delete event was posted with a scope counter of 0, and a event loop level > 1, it was assumed the deleteLater() happened from a code path not covered by a QScopedLoopLevelCounter (now renamed to QScopedScopeLevelCounter).
As the catch all mechanism was in place, many of the QScopedScopeLevelCounters were removed in follow up patches.
Relevant tests
- tst_QThread::connectThreadFinishedSignalToObjectDeleteLaterSlot()
- tst_QObject::deleteLaterInAboutToBlockHandler()
- tst_QCoreApplication::testDeleteLaterFromBeforeOutermostEventLoop()
- tst_QApplication::testDeleteLater()
- tst_QApplication::testDeleteLaterProcessEvents1()
- tst_QApplication::testDeleteLaterProcessEvents2()
- tst_QApplication::testDeleteLaterProcessEvents3()
- tst_QApplication::testDeleteLaterProcessEvents4()
- tst_QApplication::testDeleteLaterProcessEvents5()
Deferred patches
Issues to triage
Attachments
Issue Links
- relates to
-
QTBUG-132070 Ubuntu 24.04 x64: Sometimes hundreds of tests failing
- Reported
-
QTBUG-121220 Add a getter "markedForDeletion" to know if deleteLater has been called
- Closed
- resulted from
-
QTBUG-18434 QObject::deleteLater called from a function scheduled by g_idle_add --> QObject won't get deleted the next time the main loop spins
- Closed
-
QTBUG-36434 Deferred deletion in aboutToBlock/awake from an event dispatcher is broken
- Closed
-
QTBUG-32859 DeferredDelete events not handled when created by GLib dispatched events
- Closed
- resulted in
-
QTBUG-129927 [REG: 6.7 -> 6.8] Use after free in QTimeZone
- Closed
- links to