QList detach and protected data

369 views Asked by At

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

0

There are 0 answers