Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Closing a QDockWidget While the User is Actively Resizing It



  • Hey,

    I found that when a QDockWidget is closed while the users is actively dragging a separator it will trigger a segmentation fault. This is on Qt 5.9.6, I haven't tested on other versions.

    I was able to put together a minimum reproducible example:

    TestDockWidgetResizeWindow::TestDockWidgetResizeWindow(QWidget *parent)
        : QMainWindow(parent)
    {
        i = 10;
    
        QLabel * center = new QLabel("Central Widget", this);
        setCentralWidget(center);
    
        QDockWidget * dock = new QDockWidget(this);
        QLabel * dockLabel = new QLabel("10", dock);
        dock->setWidget(dockLabel);
        addDockWidget(Qt::TopDockWidgetArea, dock);
    
        QTimer * t = new QTimer();
        connect(t, &QTimer::timeout, [this, dockLabel, t, dock](){
    
            i--;
            dockLabel->setText(QString::number(i));
    
            if(!i){
                t->stop();
                t->deleteLater();
    
                dock->hide();
                dock->deleteLater();
            }
        });
    
        t->start(1000);
    }
    

    This will show a mainwindow with a dockwidget docked to the top area. Text in the dockwidget will count down from 10 and run deleteLater() when it reaches zero. If you are holding and dragging the separator that lines the bottom on the dockwidget when this happens, it will segfault.

    The segfault doesnt occur within the destructor of the dockwidget, or from anything else that my code does. The stacktrace shows it crashing in qdockarealayout.cpp at:

    bool QDockAreaLayoutItem::skip() const
    {
        if (placeHolderItem != 0)
            return true;
    
        if (flags & GapItem)
            return false;
    
        if (widgetItem != 0)
            return widgetItem->isEmpty(); <--- CRASH HERE
    

    The stacktrace also shows that it looks like this thread of execution was triggered by an event where it runs separatorMove. It seems like there are still user driven events coming from the mousing dragging which qt atempts to service with the dockwidget, but by the time the event is serviced, the dockwidget is deleted.

    At first i thought deleteLater() would solve the problem, originally we had just used close(). I've also tried adding before the call to deleteLater():

    dock->setFixedSize(dock->size());
    qApp->processEvents();
    

    Attempting to disable the ability to resize before we delete the dockwidget. But I still get the same segfault.

    I was wondering if there were any solutions I could implement to work around this problem? Or maybe Im doing something wrong?

    The reason we are programatically closing a dockwidget is because we display loading boxes to the user in the top dock area, and when the loading finishes we run the code to delete the dockwidget. The top dock area could also have other prompts for the user so theres a chance the user would want to drag one dock widget bigger to read a longer message. Worst-case, we can set a fixed size for only the loading screen dockwidgets which would eliminate most of the possible cases where the user could trigger this crash, but we'd rather maintain the resizable nature of the dockwidgets if possible.

    Thanks,
    Nick


  • Lifetime Qt Champion

    @NickJT said in Closing a QDockWidget While the User is Actively Resizing It:

    I haven't tested on other versions

    Would be interesting to test with latest Qt5 version and file a bug if reproducible.



  • Upgrading our Qt version is definitely in the backlog. We've had some trouble getting a 32bit version of 5.15 (unfortunately our internal API is still 32bit) compiled and loaded in qtcreator, but thats for another thread.
    Maybe someone with a working newer build kit can try the example code posted above, just create a new project and paste the code in the constructor.

    Really what i want is ideas on how to work around this or address it within my project. I was thinking maybe I could disable receiving of events in the dockwidget before deleting it?


  • Lifetime Qt Champion

    It also crashes with Qt5.15.2 and I would guess also with Qt6. Still don't understand why ...



  • I'm really looking for a workaround right now to prevent the user from triggering a segfault. Could I disable receiving of events on the separator just before deleting the dockwidget, and how would that be done? Thanks.



  • QMouseEvent mEvnRelease(QEvent::MouseButtonRelease,
                                QCursor::pos(),
                                Qt::LeftButton,
                                Qt::LeftButton,
                                Qt::NoModifier);
    
    QCoreApplication::sendEvent(target, &mEvnRelease);
    

    The above code force releases the mouse and resolves the crash. In this context 'target' is the QMainWindow the QDockWidget is docked to.



  • @NickJT said in Closing a QDockWidget While the User is Actively Resizing It:

    The above code force releases the mouse and resolves the crash. In this context 'target' is the QMainWindow the QDockWidget is docked to.

    Could crash, because AFAIK undocking changes the parent of the dockWidget, so there is no parent anymore to send the event to. Then it tries to send the move / drag events to the invalid parent which will produce a segfault.
    But I'm not 100% sure ;-)



  • @Pl45m4 I want to close this out for future devs looking for the right answer.
    The above solution is run before dockwidget->hide() and deleteLater().
    We aren't ever undocking the dockwidget here. As far as I understand, my solution sends an event to the QMainWindow telling it the left mouse button was released, and the mainwindow responds accordingly, in this case, it ends the resizing of the dockwidget. The reason we send the event to the qmainwindow is that it is in charge of the dockwidget separators; I figured this out from some inspection of the source code and searching online.
    Also notably, the pointer to our parent qmainwindow is guaranteed safe in my implementation. The code that closes the dockwidget is running in the scope of the qmainwindow and we are just using 'this'. It might be dangerous to use this code within the scope of the qdockwidget and get the pointer to the parent with parent().


Log in to reply