Qt: Recursive repaint detected



  • Hallo,

    ich schreibe einen Datei-Manager, der auch rumkopieren und verschieben kann. Wenn eine Kopieraktion startet, wird ein Thread und einen Fortschrittsbalken angezeigt. Wenn ich nach dem Fehler google, sehe ich bloß Threads in denen steht, man solle den Signals/Slot-Mechanismus innerhalb eines Threads benutzen, wenn man von da aus an einem Widget werkeln will. Jedoch benutze ich überwiegend den Signals/Slots-Mechanismus und werkle kein einziges Mal innerhalb des Threads an einem Widget. Hier ein paar Zeilen Code...

    #include "file_operation.hpp"
    #include <boost/system/error_code.hpp>
    
    /*** COPY OPERATION ***/
    
    void copy_operation::copy_file(const fs::path& source, const fs::path& destination){
    ...
        emit message("Copying " + QString{source.c_str()});
    
    ...
        emit processed();
    }
    
    void copy_operation::copy(const QStringList& files, const QDir& output) try{
        for(const auto& file : files)
            copy_file(file.toStdString(), output.path().toStdString());
    }
    
    catch(const std::exception& err){
        emit error(err.what());
    }
    
    /*** RENAME OPERATION ***/
    
    void rename_operation::check_error(const fs::path& file_path, const QDir& output){
    ...
    }
    
    void rename_operation::rename(const QStringList& files, const QDir& output) try{
    ...
    
        for(const auto& file : files){
    ...
    
            emit message("Moving " + file);
    ...
    
            emit processed();
        }
    }
    
    catch(const std::exception& err){
        emit error(err.what());
    }
    
    /*** REMOVE OPREATION ***/
    
    void remove_operation::remove(const QStringList& files) try{
    
        for(const auto& file : files){
    ...
            emit processed();
        }
    }
    
    catch(const std::exception& err){
        emit error(err.what());
    }
    

    Wie man sieht benutze ich ausschließlich Signals um den Fortschrittsbalken zu verändern. Der ausgepunktete Rest sind die Operationen die mittels Boost.Filesystem durchgeführt werden.
    Hier noch der Code, an dem Fortschittsbalken und Operation erstellt werden:

    auto* operation = new copy_operation{files, dir_info.directory};
        auto* widget = new file_operation_widget{operation, count};
    
        connect(widget, &file_operation_widget::on_close, widget, &QWidget::deleteLater);
        connect(operation, &QThread::finished, operation, &QObject::deleteLater);
    
        widget->show();
        operation->start();
    

    Liegt der Fehler hier vielleicht? Ich erstelle hier die Kopieroperation und den Fortschrittsbalken. In der Klasse des Fortschrittbalkens habe ich closeEvent() überschrieben, der zusätzlich einfach nur ein Signal namens on_close() auslöst. Dieses Signal verbinde ich mit deleteLater() um mir das Memory Leak zu ersparen. Beim zweiten connect() Aufruf tu ich eigentlich das selbe. Anschließend wird der Fortschrittsbalken mittels show() angezeigt und die Kopieraktion mit start() ausgeführt.

    Was mach ich falsch? Der Fehler tritt echt zu selten auf um ihn mit dem Debugger zu finden und bisher trat er nur dann auf, wenn die Operation so schnell über die Bühne lief, sodass der Ladebalken nichtmal gemalen wurde.



  • Ich seh hier jetzt noch keinen Fehler. Interessant wäre vielleicht, mit was processed verbunden wird.

    Setz einfach einen Breakpoint an der Stelle, wo der Fehler ausgegeben wird. Das mit dem recursive repaint war glaub ich direkt in qpainter.cpp.



  • Mechanics schrieb:

    Ich seh hier jetzt noch keinen Fehler. Interessant wäre vielleicht, mit was processed verbunden wird.

    void file_operation_widget::set_connections(){
        connect(operation, &QThread::finished, this, &QWidget::close);
    
        connect(operation, &file_operation::message, label, &QLabel::setText);
        connect(operation, &file_operation::error, this, &file_operation_widget::show_error);
    
        connect(operation, &file_operation::processed, [this]{
            bar->setValue(bar->value() + 1);
            QApplication::processEvents();
        });
    ...
    

    QApplication::processEvents() benutze ich, weil das Fenster bei großen Operationen manchmal nicht vollständig gemalen wird.

    Setz einfach einen Breakpoint an der Stelle, wo der Fehler ausgegeben wird.

    Ich weiß nicht wirklich wo das sein soll. Der Fehler taucht sagen wir jedes Schaltjahr mal auf und zwar mitten in der Operation und bisher auch nur, wenn die Operation so klein war, dass das Fenster nichtmal gemalen wird. Die Daten werden also nicht vollständig kopiert.



  • Das processEvents hast du grad zum ersten mal erwähnt. Das kann grundsätzlich natürlich schon zu rekursiven Aufrufen führen.
    Ich hab grad keine Lust, mich in dein konkretes Problem reinzudenken, aber grundsätzlich ist es so, dass ein Paint Event noch in der Event Queue ist, du rufst Process Events auf, und dasselbe Paint Event wird wieder abgearbeitet. Ich hab keine Ahnung, wie das bei Qt 5+ ist, aber bei Qt 4 ist es zumindest problematisch, wenn nicht ein harter Crash.



  • Mechanics schrieb:

    Das processEvents hast du grad zum ersten mal erwähnt. Das kann grundsätzlich natürlich schon zu rekursiven Aufrufen führen.
    Ich hab grad keine Lust, mich in dein konkretes Problem reinzudenken, aber grundsätzlich ist es so, dass ein Paint Event noch in der Event Queue ist, du rufst Process Events auf, und dasselbe Paint Event wird wieder abgearbeitet. Ich hab keine Ahnung, wie das bei Qt 5+ ist, aber bei Qt 4 ist es zumindest problematisch, wenn nicht ein harter Crash.

    Achso, naja, die Doku von Qt sagt jedenfalls, dass processEvents() thread-safe ist.
    Ich werds mal raushauen und schauen, ob der Fehler nochmals auftritt. Wenn er nicht mehr auftritt, dann wars das und danke dir.



  • Nein, das wars nicht, habe eben einen Segfault bekommen.
    Was für ein mieser Zufall.



  • Beim segfault müsstest du aber den Callstack sehen.



  • Der Debugger springt sofort in eine Asm-Datei, davon weiß ich leider nichts. Im Stack steht folgendes:

    1   QPainter::drawPixmap(QPointF const&, QPixmap const&)                                                                                                          0x7ffff651218b 
    2   ??                                                                                                                                                            0x7ffff6b68b1b 
    3   ??                                                                                                                                                            0x7ffff6b5b766 
    4   QPushButton::paintEvent(QPaintEvent *)                                                                                                                        0x7ffff6c1c0ee 
    5   QWidget::event(QEvent *)                                                                                                                                      0x7ffff6acf298 
    6   QApplicationPrivate::notify_helper(QObject *, QEvent *)                                                                                                       0x7ffff6a8a7bc 
    7   QApplication::notify(QObject *, QEvent *)                                                                                                                     0x7ffff6a8f95f 
    8   QCoreApplication::notifyInternal2(QObject *, QEvent *)                                                                                                        0x7ffff5403280 
    9   QWidgetPrivate::sendPaintEvent(QRegion const&)                                                                                                                0x7ffff6ac81ba 
    10  QWidgetPrivate::drawWidget(QPaintDevice *, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *)                                             0x7ffff6ac8809 
    11  QWidgetPrivate::paintSiblingsRecursive(QPaintDevice *, QList<QObject *> const&, int, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *)   0x7ffff6ac94fc 
    12  QWidgetPrivate::drawWidget(QPaintDevice *, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *)                                             0x7ffff6ac8374 
    13  ??                                                                                                                                                            0x7ffff6a987bb 
    14  ??                                                                                                                                                            0x7ffff6a989a9 
    15  QWidgetPrivate::syncBackingStore()                                                                                                                            0x7ffff6ab6f6f 
    16  QWidget::event(QEvent *)                                                                                                                                      0x7ffff6acf368 
    17  QApplicationPrivate::notify_helper(QObject *, QEvent *)                                                                                                       0x7ffff6a8a7bc 
    18  QApplication::notify(QObject *, QEvent *)                                                                                                                     0x7ffff6a8f95f 
    19  QCoreApplication::notifyInternal2(QObject *, QEvent *)                                                                                                        0x7ffff5403280 
    20  QCoreApplicationPrivate::sendPostedEvents(QObject *, int, QThreadData *)                                                                                      0x7ffff54051fc 
    ... <More>
    

    processEvents() hab ich übrigens rausgehauen.



  • Ich seh da soweit nichts großartig verdächtiges. Ist es der Hauptthread, was machen die anderen Threads grad? Kannst du an den Variablen usw. erkennen, was schiefgeht?



  • Mechanics schrieb:

    Ich seh da soweit nichts großartig verdächtiges. Ist es der Hauptthread, was machen die anderen Threads grad? Kannst du an den Variablen usw. erkennen, was schiefgeht?

    Das "Locals and Expressions"-Fenster ist leer, wenn der Debugger in die ASM-Zeile springt. Ich glaube es liegt am Mainthread, da ich wirklich keine QObject-Methoden vom Thread aus aufrufe. Wann kann ein Recursive Paint denn sonst noch auftreten? Echt nur wenn man in einem anderen Thread Objekte malt? Das ist bei mir nämlich nicht der Fall, in den Threads benutze ich ausschließlich nur Signals.

    Hier nochmal der Callstack:

    1   QTransform::type() const                                                                                                                                      0x7ffff6563080 
    2   QRasterPaintEngine::drawImage(QPointF const&, QImage const&)                                                                                                  0x7ffff64f4f15 
    3   QRasterPaintEngine::drawPixmap(QPointF const&, QPixmap const&)                                                                                                0x7ffff64f9e4e 
    4   QPainter::drawPixmap(QPointF const&, QPixmap const&)                                                                                                          0x7ffff651214b 
    5   ??                                                                                                                                                            0x7ffff6b66cb5 
    6   ??                                                                                                                                                            0x7ffff6b5a8d5 
    7   QCommonStyle::drawControl(QStyle::ControlElement, QStyleOption const *, QPainter *, QWidget const *) const                                                    0x7ffff6b05551 
    8   ??                                                                                                                                                            0x7ffff6b5b2e5 
    9   QProgressBar::paintEvent(QPaintEvent *)                                                                                                                       0x7ffff6c1b1b0 
    10  QWidget::event(QEvent *)                                                                                                                                      0x7ffff6acf298 
    11  QProgressBar::event(QEvent *)                                                                                                                                 0x7ffff6c1bcee 
    12  QApplicationPrivate::notify_helper(QObject *, QEvent *)                                                                                                       0x7ffff6a8a7bc 
    13  QApplication::notify(QObject *, QEvent *)                                                                                                                     0x7ffff6a8f95f 
    14  QCoreApplication::notifyInternal2(QObject *, QEvent *)                                                                                                        0x7ffff5403280 
    15  QWidgetPrivate::sendPaintEvent(QRegion const&)                                                                                                                0x7ffff6ac81ba 
    16  QWidgetPrivate::drawWidget(QPaintDevice *, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *)                                             0x7ffff6ac8809 
    17  QWidgetPrivate::paintSiblingsRecursive(QPaintDevice *, QList<QObject *> const&, int, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *)   0x7ffff6ac94fc 
    18  QWidgetPrivate::drawWidget(QPaintDevice *, QRegion const&, QPoint const&, int, QPainter *, QWidgetBackingStore *)                                             0x7ffff6ac8374 
    19  ??                                                                                                                                                            0x7ffff6a987bb 
    20  ??                                                                                                                                                            0x7ffff6a989a9 
    ... <More>
    


  • Ich habe mal testhalber Qt::BlockedQueuedConnection ausprobiert und der Fehler scheint bisher nicht mehr aufzutreten. Ich melde mich dann mal wieder, falls der Fehler wieder auftaucht.



  • Qtnase schrieb:

    Wann kann ein Recursive Paint denn sonst noch auftreten? Echt nur wenn man in einem anderen Thread Objekte malt?

    Nein, hatte ich ja schon geschrieben. Ich kenn mich mit Qt5 übrigens nicht explizit aus, aber es würde mich schon stark überraschen, wenn man aus mehreren Threads malen könnte. Klar, du kannst in einem Seitenthread einen QPainter erstellen und in ein QImage malen (aber nicht QPixmap in Qt4), aber aus mehreren Threads in denselben QPainter zeichnen macht keinen Sinn.

    BlockedQueuedConnection... Das ist jetzt nicht wirklich dafür gedacht, könnte aber schon Sinn machen, dass es hier damit funktioniert. Ich hätt aber eigentlich schon erwartet, einen "sauberen" Callstack zu sehen, bei dem ersichtlich ist, dass der Aufruf tatsächlich rekursiv ist. Wenn man das Problem nicht genau versteht, kann es sein, das man das jetzt kurzfristig löst, später aber noch mehr Probleme bekommt.



  • Das mit dem recursive paint event geht auch, indem man selbst paintEvent() überschreibt und dort indirekt einen neuen QPainter aufmacht, z.B. indem man einen QDialog öffnet (vorzüglich demonstriert das Jürgen Wolf in seinem Qt4-Programmieren-Buch - nicht nachmachen ;)).
    Implementierst du denn irgendwo paintEvent selber?



  • Cremant schrieb:

    Implementierst du denn irgendwo paintEvent selber?

    Nope.
    Also seitdem ich die Verbindungen mit Qt::BlockingQueuedConnection gemacht habe, trat der Fehler nicht mehr auf.


Anmelden zum Antworten