I have a code where I maintain a QList of pointers on a QThread derived class objects.
Simplified example:
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include "myplugin.h"
int main()
{
int loopcount = 0;
myPlugin* plug = new myPlugin();
// Initialization with 2 threads
plug->init(2);
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
while(true){
loopcount++;
// a little sleep
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
if(loopcount == 1) {
// simulate detach() condition
plug->genDetach();
}
// check plugin status
bool status = plug->canBeStopped();
// code using status...
// read progress
float prog = plug->getProgress();
// code using progress
}
}
Here is the plugin class:
myplugin.h
#ifndef MYPLUGIN_H
#define MYPLUGIN_H
#include "mythreadedclass.h"
class myPlugin
{
public:
myPlugin();
void init(int nbthreads);
bool canBeStopped();
float getProgress();
void genDetach();
int m_nbthreads;
bool m_detach;
private:
QList <myThreadedClass*> myThreadsList;
};
#endif // MYPLUGIN_H
myplugin.cpp
#include "myplugin.h"
#include <QDebug>
myPlugin::myPlugin()
{
m_nbthreads = 0;
m_detach = false;
}
void myPlugin::genDetach() {
// set a flag to trig the detach()
m_detach = true;
}
void myPlugin::init(int nbthreads) {
m_nbthreads = nbthreads;
myThreadedClass* newthread;
// Create as much threads as required
// and store pointers in a QList
for(int i=0; i<nbthreads;i++){
newthread = new myThreadedClass();
myThreadsList.append(newthread);
newthread->setId(i);
// start the thread
// (QThread::start() calls run()
newthread->start();
}
}
bool myPlugin::canBeStopped() {
bool bRet = true;
QList<myThreadedClass*> tmpThreadList;
// simulate unidentified event leading to a detach() in next range-loop in the real code
if(m_detach){
// This line increments reference count and lead to detach()
// in for range-loop begin() iterator
tmpThreadList = myThreadsList; // QList container copy (on write)
}
// Plugin can be stopped if all his threads can be stopped
// using a range-loop, begin() operator performs a detach()
// if another reference on the QList exists
// detach() will detect that count>1 and make a deep-copy
// of what? (the whole Qlist? the QList item? What if items are pointers?)
// How can m_progress become uninitialized???
// loop shall be changed to for (const auto &i : list) { ... } to avoid detach
for (myThreadedClass * threadInstance: myThreadsList){
// query the thread
bRet &= threadInstance->canBeStopped();
}
// on return, tmpThreadList destructor is called
// can this be a problem?
return bRet;
}
float myPlugin::getProgress() {
float threadProgress = 0;
int threadcount = myThreadsList.count();
myThreadedClass * threadInstance = nullptr;
for (myThreadedClass * threadIter: myThreadsList){
// here getProgress randomly crashes with
// "access to uninitialized value" m_progress after detach
// NOT REPRODUCED WITH THIS EXAMPLE
threadProgress = threadIter->getProgress();
}
return 0;
}
mythreadedclass.h
#ifndef MYTHREADEDCLASS_H
#define MYTHREADEDCLASS_H
#include <QThread>
class myThreadedClass : public QThread
{
public:
// Constructor
myThreadedClass();
void run(); // re-implemented; called by QThread::start()
void setId(int idnum);
bool canBeStopped();
float getProgress() const;
bool running;
int m_id;
int m_loop;
protected:
float m_progress;
};
#endif // MYTHREADEDCLASS_H
mythreadedclass.cpp
#include "mythreadedclass.h"
#include <QDebug>
// Constructor
myThreadedClass::myThreadedClass()
{
running = false;
m_loop = 1;
m_progress = 0; // initialized in the constructor
}
void myThreadedClass::setId(int idnum) {
qDebug()<<"myThreadClass::setId thread id"<<idnum;
m_id = idnum;
}
void myThreadedClass::run()
{
running = true;
while(running) {
qDebug()<<"myThreadClass::run thread id"<<m_id
<<" loop "<< m_loop++;
// Here a lot of processing code...
// modifying m_progress at the end
sleep(2);
}
}
bool myThreadedClass::canBeStopped() {
// Here code setting running value...
return (running == false);
}
float myThreadedClass::getProgress() const
{
// How can the data be uninitialized???
return m_progress; // return protected data value
}
Regularly, I parse this QList to access each of my objects.
The code can run with no problem for hours...
But randomly, the programm crashes on the second loop (in getProgress), with a message saying that I try to access to uninitialized data in myThreadedClass::getProgress(). The data in question is a m_progress, a protected member initialized in the constructor. (I have no idea if it is linked to the fact it is protected...)
I have catched the segfault with valgrind and gdb, and it appears that because I use a for range-loop to parse the QList (in canBeStopped()), a detach() occurs.
valgrind output:
==4518== Use of uninitialised value of size 8
==4518== at 0xB6C7608: xxx::getProgress() const (xxx.cpp:42)
...
...
==4518== Uninitialised value was created by a heap allocation
==4518== at 0x4A06A2E: malloc (vg_replace_malloc.c:270)
==4518== by 0x300AC8DB4C: QListData::detach3() (in /usr/lib64/libQtCore.so.4.6.2)
==4518== by 0xB6C27FC: QList<myThreadedClass*>::detach_helper() (qlist.h:604)
==4518== by 0xB6C110B: QList<myThreadedClass*>::detach() (in xxxxxxxx.so)
==4518== by 0xB6BF1C1: QList<myThreadedClass*>::begin() (qlist.h:248)
==4518== by 0xB6BD49B: xxx::canBeStopped() (xxx.cpp:691)
(I have to analyze parts of my code to identify the other reference to my object that lead to the detach, but I can simulate it within the example.)
So my question: How can the detach() in the 1st loop make the m_progress variable become uninitialized in the 2nd loop???
could the deep-copy occuring with the detach be incomplete?
And for my information, can someone explain what is exacly duplicated during detach(), when the QList only stores pointers to objects? How can I trace or identify the original and the copy, print their address or something?
Context: QT4.8.7 SL6 or Centos8.3 or Ubuntu