Uploaded image for project: 'Qt Creator'
  1. Qt Creator
  2. QTCREATORBUG-25385

Creator deadlocks on shutdown, just after session switch

    XMLWordPrintable

    Details

    • Commits:
      675a72e29648844eca625202ee56da9280b6ac22 (qt-creator/qt-creator/4.15)

      Description

      If Creator is being shut down just after session switch, and a new session contains a big project (like creator), the Creator may deadlock. All the windows are being closed, but Creator doesn't finish.

      It looks like sometimes QCoreApplication::aboutToClose() signal is not sent when it's being expected. When attaching debugger to the still running Creator, it shows that QCoreApplication's event loop is not finished and is still processing events. That's why the aboutToClose() signal is not emitted.

      OTOH, MainWindow::closeEvent() was executed, and the passed event was accepted.

      So it looks like something is preventing the QCoreApplication's event loop from stopping execution.

      Update:

      After the MainWindow::closeEvent() is executed, the Qt posts QEvent::Quit to the QCoreApplication object. As long as this event is awaiting in queue to be processed (which ultimately cause aboutToClose() signal emission) there shouldn't be any calls to QCoreApplication::removePostedEvents(qApp, 0) (or alternatively QCoreApplication::removePostedEvents(qApp, QEvent::Quit)), otherwise the quit event will get removed.

      The implementation of QEventLoop::exec() contains:

          ....
          // remove posted quit events when entering a new event loop
          QCoreApplication *app = QCoreApplication::instance();
          if (app && app->thread() == thread())
              QCoreApplication::removePostedEvents(app, QEvent::Quit);
          ....
      

      This means, that if in meantime someone will execute a new event loop in the main thread, we may hit the mentioned deadlock.

      In fact it's the case. I've traced, that after leaving MainWindow::closeEvent() and before the Quit event is processed, we are calling a QEventLoop::exec() in main thread, probably as a result of processing pending events.
      This is done from: void CMakeBuildSystem::runCTest():

          const SynchronousProcessResponse response = ctest.run(cmd);
      

      which calls in turn SynchronousProcess::run():

              d->m_eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
      

      This explains the reached deadlock.

      So, the order of execution in main thread is as follows:
      1. void CMakeBuildSystem::combineScanAndParse(), which queues a call to &CMakeBuildSystem::runCTest
      2. MainWindow::closeEvent(), which queues the QEvent::Quit
      3. The first in queue was CMakeBuildSystem::runCTest, so it is called now, which erases the QEvent::Quit from QCoreApplication awaiting events queue.
      4. We are in deadlock.

      Update on how to reproduce it in 100%:

      Apply the following patch, which introduce a 1 second sleep in the right point in time:

      diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
      index abd515f1a0..07086084cc 100644
      --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
      +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
      @@ -70,6 +70,8 @@
       #include <QPushButton>
       #include <QRegularExpression>
       
      +#include <QThread>
      +
       using namespace ProjectExplorer;
       using namespace Utils;
       
      @@ -555,6 +557,9 @@ void CMakeBuildSystem::combineScanAndParse()
       
           emitBuildSystemUpdated();
       
      +    qDebug() << "Scheduling CMakeBuildSystem::runCTest()";
      +    qApp->thread()->sleep(1);
      +
           QTimer::singleShot(0, this, &CMakeBuildSystem::runCTest);
       }
       
      @@ -893,6 +898,7 @@ int CMakeBuildSystem::takeReparseParameters()
       
       void CMakeBuildSystem::runCTest()
       {
      +    qDebug() << "Executing CMakeBuildSystem::runCTest()";
           if (!cmakeBuildConfiguration()->error().isEmpty() || m_ctestPath.isEmpty()) {
               qCDebug(cmakeBuildSystemLog) << "Cancel ctest run after failed cmake run";
               emit testInformationUpdated();
      

      Steps to reproduce (with this patch applied):
      1. Open a session
      2. While the session is being loaded observe the debug output of Creator you are running. When you notice a debug printout "Scheduling CMakeBuildSystem::runCTest()" quickly close the creator (you have only 1 second for it). Now you are in deadlock

        Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

          Activity

            People

            Assignee:
            jkobus Jarek Kobus
            Reporter:
            jkobus Jarek Kobus
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Dates

              Created:
              Updated:
              Resolved:

                Gerrit Reviews

                There are no open Gerrit changes