Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-120124

Improvements to deferred deletions / deleteLater()

    XMLWordPrintable

Details

    • Epic
    • Resolution: Unresolved
    • P3: Somewhat important
    • None
    • dev
    • Core: Object Model
    • None
    • deleteLater()
    • All
    • 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

      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

      https://bugreports.qt.io/issues/?jql=text%20~%20%22deleteLater%22%20and%20status%20not%20in%20(closed)

       

      Attachments

        Issue Links

          For Gerrit Dashboard: QTBUG-120124
          # Subject Branch Project Status CR V

          Activity

            People

              vestbo Tor Arne Vestbø
              vestbo Tor Arne Vestbø
              Votes:
              0 Vote for this issue
              Watchers:
              1 Start watching this issue

              Dates

                Created:
                Updated: