I am going around in circles and hope someone can help. I have a much more complicated routine but have been able to distill it, mostly, so I can post the code.

The core of the problem is I am using poppler-qt to render PDF's, and that provides a QImage as output of a page. I am rendering in many threads at once, and trying to pass the images back to the main thread via signals/slots. This is modeled, somewhat, from the Mandelbrot example, though it immediately loads the QImage into a Pixmap and does not use it again. I need to save many of them.

These are loaded to an array of QImage*, and when I want to replace one (e.g. different scaling) I want to appropriately delete the old. I say appropriately as it is a bit unclear if I need to delete it manually, but if I do not it leaks memory (hugely so, not subtle, as shown in this loop below).

If I delete it, I get a segmentation fault and crash. I have read other threads about this, but without clear solution (most steered to alternative approaches).

It seems specific to the threading; if I do a fake threaded routine instead (i.e. call on the main thread), still using a signal/slot, the delete works. That routine is shown also but the call commented.

The failure is a bit fragile in this distilled version. Some little things, like removing a widget update, can make it work, sometimes, at least for a while. But it is almost completely reproducible for me in this code. Build has c++11 added as a config option.

This is happening with QT 5.5.1 (GCC 5.2.1 20151129) on Ubuntu 16.04 in a HyperV VM Guest, with 16GB memory and 3 cores. It uses the distro version of everything involved, and is up to date as of today.

The program is written to also be a stress test of sorts, and continue requesting pages rapidly on the main thread; it never uses them, it just deletes them when the next is requested.

I've left in a few comments showing some alternatives, e.g. allocating the QImage in the thread with new vs a local copy, that seem to make no difference (other than a need to adjust from pointers to non in the associated code).

As best I can tell, other than the possible shallow copy of the QImage, I do not think the worker thread is accessing anything that the primary thread is using. I have read about the shallow copy feature of QImage, and the double buffered aspect of the signal, but without it really helping me understand if this is a permitted mechanism or not (but then there's that official sample that seems to do the same).

My assumption is the failure is due to deleting something that no longer exists -- but if so, why does it build up memory usage indefinitely if I do not delete it? Is that double buffering perhaps breaking the reference count links?

Is there a more appropriate way to get that QImage back to the main thread? Should I pass in the address of where I want it, and let the thread load it directly? (and protect it with a mutex?)?

If anyone is bored enough to run the code, does it work for you, i.e. fail?

Thanks,

Linwood

mainwindow.h

Qt Code:
  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3.  
  4. #include <QMainWindow>
  5. #include <QLabel>
  6. #include <QImage>
  7. #include <QWidget>
  8. #include <QTimer>
  9. #include <QApplication>
  10.  
  11.  
  12. #include "renderthread.h"
  13.  
  14. class MainWindow : public QMainWindow
  15. {
  16. Q_OBJECT
  17.  
  18. public:
  19. MainWindow(QWidget *parent = 0);
  20. void doStuff();
  21. private:
  22. QLabel outer;
  23. QTimer t;
  24. void requestPage();
  25. const QImage *pageImages;
  26. int pagesRequested;
  27. int pagesRetrieved;
  28. renderThread *pageThreads;
  29. bool pageThreadActive;
  30. private slots:
  31. void updateImage(const QImage &image);
  32. };
  33. #endif // MAINWINDOW_H
To copy to clipboard, switch view to plain text mode 

renderthread.h

Qt Code:
  1. #ifndef RENDERTHREAD_H
  2. #define RENDERTHREAD_H
  3.  
  4. #include <QThread>
  5. #include <QMutex>
  6. #include <QImage>
  7. #include <QWaitCondition>
  8. #include <cassert>
  9.  
  10.  
  11. class renderThread : public QThread
  12. {
  13. Q_OBJECT
  14.  
  15. public:
  16. renderThread(QObject *parent = 0);
  17. void render();
  18. void fake_run();
  19.  
  20. signals:
  21. void renderedImage(const QImage &image);
  22.  
  23. protected:
  24. void run() Q_DECL_OVERRIDE;
  25.  
  26. private:
  27. QMutex mutex;
  28. QWaitCondition condition;
  29. // QImage theImage;
  30. QImage *theImagePtr;
  31. };
  32.  
  33. #endif // RENDERTHREAD_H
To copy to clipboard, switch view to plain text mode 

main.cpp

Qt Code:
  1. #include <QApplication>
  2. #include "mainwindow.h"
  3.  
  4.  
  5. int main(int argc, char *argv[])
  6. {
  7. QApplication a(argc, argv);
  8. MainWindow w;
  9. w.setMinimumSize(600,20);
  10. w.show();
  11. return a.exec();
  12. }
To copy to clipboard, switch view to plain text mode 

mainwindow.cpp

Qt Code:
  1. #include "mainwindow.h"
  2.  
  3. MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
  4. {
  5. outer.setParent(this);
  6. this->setCentralWidget(&outer);
  7.  
  8. pageImages = NULL;
  9. pageThreads = new renderThread();
  10. connect(pageThreads, SIGNAL(renderedImage(const QImage &)), this, SLOT(updateImage(const QImage &)) );
  11. pageThreadActive=false;
  12. pagesRequested=0;
  13. pagesRetrieved=0;
  14.  
  15. t.setInterval(1);
  16. connect(&t,&QTimer::timeout, this, [this]{this->doStuff();});
  17. t.start();
  18. }
  19. void MainWindow::doStuff()
  20. {
  21. requestPage();
  22. outer.setText(QString("Requested=%1, Retrieved=%2").arg(pagesRequested).arg(pagesRetrieved));
  23. outer.update(); // If this is removed the delete works also.
  24. }
  25.  
  26. void MainWindow::requestPage()
  27. {
  28. if(!pageThreadActive)
  29. {
  30. pageThreadActive=true;
  31. //pageThreads->fake_run(); // this does a retrieve without using the thread, and works with the delete in place
  32. pageThreads->render(); // << this threaded one works only if delete is not in place below
  33. pagesRequested++;
  34. }
  35. }
  36.  
  37. // Slot
  38. void MainWindow::updateImage(const QImage &image)
  39. {
  40. if(pageImages)
  41. {
  42. delete pageImages; // << If removed no crash but bad memory leak, if in place crash (sometimes)
  43. pageImages=NULL;
  44. }
  45. pageImages= &image;
  46. pageThreadActive = false;
  47. pagesRetrieved++;
  48. }
To copy to clipboard, switch view to plain text mode 

renderthread.cpp

Qt Code:
  1. #include "renderthread.h"
  2.  
  3. renderThread::renderThread(QObject *parent): QThread(parent)
  4. {
  5. }
  6.  
  7. void renderThread::render()
  8. {
  9. mutex.lock(); // shouldn't be necessary but want to make sure the emit completes before we issue wakeOne
  10. if(!isRunning()) start(LowPriority);
  11. else condition.wakeOne();
  12. mutex.unlock();
  13. return;
  14. }
  15.  
  16. void renderThread::run()
  17. {
  18. forever
  19. {
  20. // theImage = QImage(1000,1000,QImage::Format_ARGB32_Premultiplied); // same thing happens if declaration is not a pointer and appropriate other changes made
  21. theImagePtr = new QImage(1000,1000,QImage::Format_ARGB32_Premultiplied);
  22. mutex.lock();
  23. emit renderedImage(*theImagePtr);
  24. condition.wait(&mutex);
  25. mutex.unlock();
  26. }
  27. }
  28.  
  29. void renderThread::fake_run()
  30. {
  31. theImagePtr = new QImage(1000,1000,QImage::Format_ARGB32_Premultiplied);
  32. emit renderedImage(*theImagePtr);
  33. }
To copy to clipboard, switch view to plain text mode