qt - debugowanie dużego projektu

0

Witam!

Mam napisany dość spory projekt w QT c++, podczas tworzenia aplikacji, była ona uruchamiana tylko na chwilę w celu sprawdzenia i dalszego kodzenia.
Teraz przy prawie skończonym projekcie, okazuje się że się wysypuje po 5min czasami po 1h. Nie widzę po zużyciu pamięci ani cpu niczego podejżanego.
Aplikację kompiluję jako "Debug", a informację jaką dostaje to:

Program nieoczekiwanie przerwał pracę.

Środowisko QT jest dla mnie czymś nowym, nie mam bladego pojęcia jak za to się zabrać. Gdyby aplikacja wysypywała się częściej, pewnie bym coś wykombinował.
Jak zrobić, aby QT wyświetliło więcej informacji związanych z problemem?

0

Uruchomiłem aplikację poprzez F5, wyskoczył komunikat(po 2h):

Podproces zatrzymany, ponieważ otrzymał on sygnał z systemu operacyjnego.

Nazwa sygnału: SIGSEGV
Znaczenie sygnału: Segmentation fault

Nadal to mało informacji...

0

Odpaliłem ponownie, już po 20min się wysypało. W tym miejscu podłubałem w aplikacji:
qt-debug1.png
Dalej trochę mało... ;)
Jeśli dobrze rozumiem, problem jest z QListData, lecz nigdzie "jawnie" z niego nie korzystam.

0

To na dole to jest call stack? Jeśli tak, to idź w górę aż trafisz "co Twoje" ;)...

3
alagner napisał(a):

To na dole to jest call stack? Jeśli tak, to idź w górę aż trafisz "co Twoje" ;)...

Na obrazku widać, że call stack nie zawiera jego kodu. Problem jest poważny.
Najwyraźniej coś pochrzanione jest z zarządzaniem pamięci (usunięto czegoś co nie powinno, albo trzyma wskaźnik do martwego obiektu).
Nie ma wyjścia najlepiej skorzystać z valgrind (wbudowany w Qt Creator, choć nie jestem pewien jak jest pod windows), albo innego narzędzia do kontroli pamięci: Electric Fence, Address Sanitizer (dostępne jako opcja kompilatora, clang, g++).

Może wystaw cały kod, najlepiej przez github, jak nie to przez zip. I opisz co trzeba klikać by odtworzyć problem.


wygląda na to, że źle korzystasz z jakiegoś timer-a
0

Program wymaga podłączenia pewnego urządzenia do działania, coś muszę wykombinować.
Czasami drobne modyfikacje powodują zmniejszenie prawdopodobieństwa wysypania się aplikacji.
Np. dodanie przycisku na formatkę spowodowało wywalenie programu raz na 13h.

Z tymi timerami też mnie trochę to zastanawia, są rozwiązaniem tymczasowym, a wygląda to tak:

Założenie
Klasa która przyjmuje dane i wyświetla je na wykresie. Nie wszystkie dane muszą być wyświetlane, ma być zapewnione odświeżanie wykresu gdzieś w okolicy 20 klatek/s

void ExChart::setData(const QVector<double> &data)
{
    if(!mDataMutex.tryLock()) return; // jesli ktos zablokowal mutex, wyjdz - gubimy dane
    mData = data; // kopia danych do klasy
    mDataMutex.unlock(); // zezwolenie na przetwarzanie danych z klasy(mData)
    if(!mRefreshTimer.isActive()) // jesli timer nieaktywny, odpal
        mRefreshTimer.start();
}

Funkcja timeout timera z interwałem 50ms


void ExChart::refreshTimeout()
{
    QMutexLocker ml(&mDataMutex); // zablokuj dostep do danych
    mRefreshTimer.stop(); // zatrzymaj timera - odpalany jest ponownie gdy przyjda dane

    compute(mDataPlot, mData); // przetworzenie danych mData do mDataPlot

    ml.unlock(); // wczesniejsze zwolnienie dostepu do danych (mData)
    updateDataPlot(mDataPlot); // przygotowanie wykresu, narysowanie QPixmap
    update(); // update "wyglądu" widgeta
}

Kod nie jest optymalny, ale skupmy się nie na błędach myślowych, tylko na wysypie aplikacji.

"compute" będzie w przyszłości na wątku, ponieważ obecne rozwiązanie blokuje trochę aplikację, lecz nie jest to teraz problemem.
Początkowo pisząc mutexy w timerze, pomyślałem że jest to błędne i może powodować problemy, ale szybki test samych timerów z mutexami nie ujawnił niczego podejżanego.
Klasa "ExChart" jest nakładką na gotową "bibliotekę" z wykresami.
W aplikacji występuje 6 wątków i 3 timery(3 wykresy), wszystko ładnie mutexami zabezpieczone.

Powyższe przykłady reprezentują jedyne wykorzystanie timerów w aplikacji.

Może jakieś sugestie na co zwrócić uwagę jeśli w trybie debug znów się wysypie?

Odpaliłem Valgrind'a i 0 problemów, nie doczekałem się także wysypu...
Będę sporo przerabiał, może wyklucze problem, ale jestem bardzo ciekaw co powoduje wysypkę.

0

Widzę, że używasz wielowątkowości. To tłumaczy, czemu masz crash po tak długim czasie i czemu na valgrind nie możesz tego odtworzyć.
valgrind ma też narzędzie do wykrywania race condition (uruchamia się go inaczej), zacznij od tego.

Poza tym, jak masz Qt to najprościej i najbezpieczniej komunikować się między wątkami za pomocą sygnałów i slotów.
To ExChart::setData jest doskonałym przykładem na to.

0

Odpaliłem aplikację na całą noc, nic się nie wysypało, a dopiero przy zamykaniu aplikacji(nie zawsze się tak dzieje).
Wszystkie połączenia wątków mam za pomocą sygnałów i slotów.

Dostałem coś takiego(call stack):

/* ... */
QTypedArrayData<double>::deallocate // <-
QVector<double>::feeData
QVector<double>::~QVector
QVector<double>::operator=
Worker::setData
Worker::qt_static_metacall
/* ... */

Zastanawia mnie jedno, do wątków(nazwę Worker) przychodzą dane i wychodzą:

/* Worker : public QThread */

void Worker::setData(const QVector<double> &data)
{
    mWrite.acquire();
    mData = data;
    mRead.release();
}

// gdzie mWrite i mRead są jako "private" w klasie:
    QSemaphore mWrite;
    QSemaphore mRead;

// oraz w konstruktorze klasy:
Worker::Worker(QObject *parent) :
   QThread(parent),
   mWrite(1), 
   mRead(0)
   /* ... */
{
// ... //

Z kolei wątek wygląda tak:

void Worker::run()
{
    QVector<double> out;
    for(;;){
        mRead.acquire();
        if(mTerminate) break;
        compute(out, mData); // jakies operacje
        mWrite.release();
        emit dataChanged(out);
    }
}

Destruktor zaś wygląda tak:

Worker::~Worker()
{
    mTerminate = true;
    mRead.release();
    if(!this->wait(500)){
        qWarning("Thread deadlock detected!");
        this->terminate();
        this->wait();
    }
}

I tutaj chyba widzę problem, jeśli klasa jest niszczona, a wywoływana jest funkcja setData, prawda?

Jeśli jest jeden wątek, to jeszcze jakoś to rozumiem, ale jak jest kilka to faktycznie może być takie coś, że:
1. Klasa jest niszczona, zniszczyła mData
2. Wywołuje się setData który wrzuca dane do mData

Połączenia mam zrobione jako "Qt::DirectConnection".

2

Problemem jest dziedziczenie po QThread.
http://blog.qt.io/blog/2010/06/17/youre-doing-it-wrong/

0

Jak to dobrze rozwiązać?

Rozumiem że poprzez:

moveToThread(this);

Slot w wątku będzie wykonany przez wątek lecz muszę odpalić:

void Worker::run(){
  exec();
}

Jeśli potrzebuję coś w wątku coś ciągle robić, np. generować dane, funkcji:

exec();

nie mogę wykonać, ponieważ uniemożliwi mi to "tworzenie" nowych danych.
Istnieje u mnie taki jeden wątek który uruchamia niskopoziomową bibliotekę która generuje dane.

void QGen::callback(unsigned char * buf, int len){
    compute(mData, buf, len);
    emit dataChanged(mData);
}

static void gen_callback(unsigned char * buf, uint32_t len, void * ctx){
    ((QGen*)ctx)->callback(buf, (int)len);
}

void QGen::run()
{
    gen_read_async(dev, gen_callback, this/* ctx */, 0, 512);
}

Więc utworzenie:

moveToThread(this);

Wymusi ode mnie, aby sygnały kierowane do wątku QGen, były bezpośrednie "Qt::DirectConnection".
Ale w tym wypadku jest sens przechodzenia?

moveToThread(this);

Doskonale zdaję sobię sprawę, że przy obecnym rozwiązaniu, sloty wykonywane są przez wątki które te sloty wywołały, dlatego zastosowałem mutex'y czy semafory, aby nie było kolizji z wątkiem-run.

Kolejne wątki odbierają dane, przetwarzają i wysyłają dalej.
Znów:

moveToThread(this);

Spowoduje to, że sloty będą musiały mieć "Qt::DirectConnection", aby przez "run()" przetwarzać dane i emitować je dalej.
Mogę także w run wrzucić exec a w slocie przetwarzać dane i emitować dalej:

void Worker::slot(QVector<double> data){ // juz nie moze byc referencji, tylko przekopiowanie danych
  MutexLocker ml(&mDataMutex); // jest niekonieczne ponieważ funkcja "slot" wykonywana jest przez wątek "Worker"
  compute(mData, data);
  emit nextSignal(mData);
}

// wyglad run:
void Worker::run(){
  exec();
}

Oczywiście muszę sobie poradzić z problemem:

QObject::connect: Cannot queue arguments of type 'QVector<double>'
(Make sure 'QVector<double>' is registered using qRegisterMetaType().)

Trochę zawiłe i kilka możliwości do przetestowania.
Jak to najlepiej zrobić?

0

W przykładach QT znalazłem takie coś:

FileManager::FileManager(QObject *parent)
    : QThread(parent)
{
    quit = false;
    totalLength = 0;
    readId = 0;
    startVerification = false;
    wokeUp = false;
    newFile = false;
    numPieces = 0;
    verifiedPieces.fill(false);
}

QBitArray FileManager::completedPieces() const
{
    QMutexLocker locker(&mutex);
    return verifiedPieces;
}

void FileManager::run()
{
    if (!generateFiles())
        return;

    do {
        {
            // Go to sleep if there's nothing to do.
            QMutexLocker locker(&mutex);
            if (!quit && readRequests.isEmpty() && writeRequests.isEmpty() && !startVerification)
                cond.wait(&mutex);
        }

        // Read pending read requests
        mutex.lock();
/// ... ///

Jest to dość analogicznie zrobione co do mojego kodu.

0
Zaspany człowiek napisał(a):

Jak to dobrze rozwiązać?

Rozumiem że poprzez:

moveToThread(this);

Jeśli this jest QThread to zdecydowanie NIE! nigdy nie przenoś QThread do samego siebie!

0

Czy myślimy o tym samym?
Jeśli pisałem o:

moveToThread(this);

Miałem na myśli klasę która dziedziczy po QThread:

class Worker: public QThread{

public:
  Worker(){
    moveToThread(this);
  }
}

zamiast:

class Worker: public QThread{

public:
  Worker(QObject * parent=0) : QThread(parent)
  {
  }
}
0

W poście uciekł mi Q_OBJECT ;)

1

Może dla lepszego zrozumienia problemu z dziedziczeniem po QThread przejrzyj to: https://mayaposch.wordpress.com/2011/11/01/how-to-really-truly-use-qthreads-the-full-explanation/

0

No tak, jest kilka rozwiązań, ale co jest w tym złego?

// uwaga, pisane z palca
#include <QThread>
#include <QVector>
#include <QSemaphore>
class Worker : public QThread
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = 0) :
        QThread(parent),
        mWrite(1),
        mRead(0),
        mTerminate(false)
    {
        start();
    }

    ~Worker()
    {
        mTerminate = true;
        mRead.release();
        if(!this->wait(500)){
            qWarning("Thread deadlock detected!");
            this->terminate();
            this->wait();
        }
    }

protected:
    QSemaphore mWrite;
    QSemaphore mRead;
    bool mTerminate;
    QVector<double> mData;

    void compute(QVector<double> &out, const QVector<double> &data){
        // jakies obliczenia
        out = data; // dla przykladu
    }

    void run(){
        QVector<double> out;
        for(;;){
            mRead.acquire();
            if(mTerminate) break;
            compute(out, mData);
            mWrite.release();
            emit dataChanged(out);
        }
    }

signals:
    void dataChanged(const QVector<double> &data);

public slots:
    void setData(const QVector<double> &data)
    {
        mWrite.acquire();
        mData = data;
        mRead.release();
    }
};

setData jest wykonywane na wątku który go wykonał i tak ma być.

Jest możliwość, że jak tworzę jakieś rzeczy w wątki to mam wyciek stosu (bo jest np. mały) ?

0

Samo dziedzicznie nie jest problemem.
Problemem jest to, że dziedzicząc ludzie nieświadomie idą na skróty i dodają funkcjonalność do QThread, która powinna być przywiązana do tego wątku a tymczasem podstawa funkcjonalność QThread nie może być przywiązana do samego siebie.

Na dodatek, jeśli workera piszę się bez dziedziczenia po QThread to o wiele łatwiej napisać testy do niego, co współcześnie jest niezbędne (w poważnych projektach kodu testującego jest 2-3 razy więcej niż kodu produkcyjnego).
Jeśli dziedziczy się po QThread to napisanie do tego dobrych testów graniczy z cudem.

Podziel się kodem, to może coś ci poprawię, jeśli będę miał czas (kiedyś robiłem dokładnie to samo co ty, oprogramowanie kontrolujące pomiary).

1 użytkowników online, w tym zalogowanych: 0, gości: 1