3

I use the following code to talk to a USB-serial port device:

#include "masterthread.h"
#include <QtSerialPort/QSerialPort>
#include <QTime>
#include "Windows.h"
#include "Psapi.h"
#include <QDebug>
QT_USE_NAMESPACE

MasterThread::MasterThread(QObject *parent)
: QThread(parent), waitTimeout(0), quit(false)
{
}

MasterThread::~MasterThread()
{
    mutex.lock();
    quit = true;
    cond.wakeOne();
    mutex.unlock();
    wait();
}

void MasterThread::run()
{
    bool currentPortNameChanged = false;

    QSerialPort serial;
    serial.setPortName("COM3");
    serial.setBaudRate(57600);
    serial.setStopBits(static_cast<QSerialPort::StopBits>(1));
    serial.setDataBits(static_cast<QSerialPort::DataBits>(8));
    serial.setParity(static_cast<QSerialPort::Parity>(0));
    serial.open(QIODevice::ReadWrite);

    //Tell the serial port connected device to start talking
    //--------------------------------------
    const char init[] = { 0x0d, 0x0d, 0x0d };
    serial.write(init, sizeof(init));
    const char* cmd = "mavlink stop\n";
    serial.write(cmd, strlen(cmd));
    serial.write(init, 2);
    cmd = "uorb start";
    serial.write(cmd, strlen(cmd));
    serial.write(init, 2);
    cmd = "sh /etc/init.d/rc.usb\n";
    serial.write(cmd, strlen(cmd));
    serial.write(init, 4);
    serial.waitForBytesWritten(100);

    int i = 0;
    int j = 0;
    forever
    {

        //Write test data out
        //-----------------------------
        QByteArray test(2000, 't');
        serial.write(test);
        bool check = serial.waitForBytesWritten(100);
        if (!check)
        {
            qDebug() << "FAIL: " << j++;
        }

        if (serial.waitForReadyRead(20))
        {
            QByteArray responseData = serial.readAll();
            while (serial.waitForReadyRead(10))
                responseData += serial.readAll();

            QString response(responseData);
            qDebug() << response;
        }
        QThread::msleep(20);

        //Print memory usage
        //---------------------------------------------------
        if (i++ % 10 == 0)
        {
            PROCESS_MEMORY_COUNTERS memcount;
            if (!GetProcessMemoryInfo(GetCurrentProcess(), &memcount, sizeof(memcount))) return;
            qDebug()<<"----------------------------" << memcount.WorkingSetSize / 1024 << "KB memory used";
        }
    } // end foever

    qDebug() << "Exiting forever loop";
}

with a simple main.cpp as:

#include <QApplication>
#include "masterthread.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MasterThread thread;
    thread.start();
    return app.exec();
}

But the memory usage keeps increasing, like 5~10MB per hour as if there are some leakage. The device is suppose to be connected for days and weeks...

What am I doing wrong here? I am on Qt5.6 windows7 debug

Nyaruko
  • 4,329
  • 9
  • 54
  • 105
  • 2
    if you are running in a thread with a Qt message loop then you could stand to call `QCoreApplication::processEvents();` before `QThread::msleep(20);` – PeterT Jul 05 '16 at 10:31
  • I dont really see a need to call QCoreApplication::processEvents() in a thread which is not using loop... I think it's wrong suggestion – evilruff Jul 05 '16 at 11:29
  • which Qt version are you using? your measurments - is it debug/release build? – evilruff Jul 05 '16 at 11:32
  • 2
    @evilruff if some of the Qt Objects internally use `deleteLater()` or so some other mechanic relying on the event loop I could see it leading to this behavior. I don't know Qt well enough to precisely know which Objects rely on the event loop and which don't, but if you have more experience feel free to share your ideas. – PeterT Jul 05 '16 at 11:57
  • 1
    case in point, processEvents would prevent this code to be affected by this bug in Qt 5.5 https://bugreports.qt.io/browse/QTBUG-48653 – PeterT Jul 05 '16 at 12:26
  • But I am on qt5.6, which seems the bug is already fixed? – Nyaruko Jul 05 '16 at 13:17
  • 2
    @Nyaruko it still seems to use other things like asynchonous signals with [QTimer](http://code.qt.io/cgit/qt/qtserialport.git/tree/src/serialport/qserialport_win.cpp?h=v5.6.0#n582) internally which might still explain the memory, that was just an example. Are you saying that the apparent memory leak is still there even with an occasional call to `QCoreApplication::processEvents();`? – PeterT Jul 05 '16 at 13:29
  • 1
    Seems to solve it, can you write a formal answer so I could accept it? – Nyaruko Jul 05 '16 at 14:03
  • 2
    There's absolutely no reason for you to implement this code by reimplementing a thread. To refactor: put it all into a `QObject`, the `forever` part belongs in a slot/functor called from a zero timeout timer, then get rid of `waitFor` and use signal/slot connections to `QIODevice` signals instead. This will lead to much cleaner, more performant, declarative-style code. See e.g.[this answer](http://stackoverflow.com/questions/32486198/sending-a-sequence-of-commands-and-wait-for-response/32595398) or [that answer](http://stackoverflow.com/a/38104235/1329652) for inspiration. – Kuba hasn't forgotten Monica Jul 07 '16 at 16:02
  • @KubaOber, thanks, I fully understand the meaning of that. I am just curious about whether this(the memory leak I have met) is a single Qt Bug, or it says in the future, all usage of Qt classes without event loop shall be very careful or even impossible. I write this blocking code actually by following Qt's official doc! – Nyaruko Jul 07 '16 at 17:30
  • 1
    You've not shown yet that you have a memory leak. You should be able to point to a particular allocation that never gets freed. `GetProcessMemoryInfo` doesn't tell you much really. 5~10MB/hour could be due to address space fragmentation and not leaks. Get a debug Qt build, and use it with a memory debugger. – Kuba hasn't forgotten Monica Jul 07 '16 at 17:54
  • @KubaOber Thanks! But after I added the processEvents() call, or as you said, change this to an async mode. No memory increase at all! – Nyaruko Jul 07 '16 at 17:55
  • 1
    The `QSerialPort` object is sent events, but you never process these events. That was the problem, then. As an aside, your thread quit code is reinventing the wheel. Use `QThread::isInterruptionRequested` and `QThread::requestInterruption` instead of a custom flag. Even if you were to write the synchronous code, the way you do it is full of cargo cult. Most of the code isn't necessary. – Kuba hasn't forgotten Monica Jul 07 '16 at 17:57
  • @KubaOber It seems so, and that is also my concern here. If any Qt class would send event out, does it mean they can not be used without a event loop? That just render a lot of Qt's official example wrong... – Nyaruko Jul 07 '16 at 18:00
  • 1
    Generally speaking, a `QObject` is not fully functional without a running event loop. That should be self-evident. Timers won't work, events won't be freed, `deleteLater` doesn't work, etc. Qt's example code isn't always fully correct as if it was a stand-alone application. You still have to have full understanding of what it is that you're doing. Examples don't absolve you from the responsibility for your product. I've been saying over and over that `waitFor` methods are so hard to use correctly as to be useless. Don't use them. There's no need. Ever. I mean it. – Kuba hasn't forgotten Monica Jul 07 '16 at 18:07

2 Answers2

2

Many Qt Components have an implicit dependency on its event loop.

While you are starting the main threads event loop with the call to app.exec(); you are not handling events generated by the QObjects created in the QThread MasterThread thread;. The details and nuances of Event handling in Qt are very well described on this page: https://wiki.qt.io/Threads_Events_QObjects#Threads_and_QObjects

But the solution boils down to: if you want to be able to process queued up Qt events in a thread where you are processing some long-running task you should call QCoreApplication::processEvents(); from time to time. This will prevent Qt events from endlessly queueing up.

PeterT
  • 7,981
  • 1
  • 26
  • 34
  • Again, is it generally safe to call QCoreApplication::processEvents() in the run()? Assume that the corresponding thread doesnot have any slot attached to it. – Nyaruko Jul 05 '16 at 18:01
  • @Nyaruko yes `processEvents` is thread-safe and does nothing if the calling thread does not have a Qt event loop (i.e. if you are using a raw `std::thread` or something else that is not a QThread). – PeterT Jul 05 '16 at 19:04
  • But if I use QThread without event loop is that OK? (I re-implemented the run() function) – Nyaruko Jul 06 '16 at 06:42
  • @Nyaruko I am not certain how that is possible. Qt autmatically "adopt"s a thread as soon as you use certain (or maybe all, haven't checked) Qt Objects in it or call `QThread::currentThread()`. You might try to manually use [`moveToThread`](http://doc.qt.io/qt-5/qobject.html#moveToThread) to move all the Objects that you create into the event loop of the main thread. But then you have to check if the QObjects you are using are thread-safe (because their slots would be executed in another thread) – PeterT Jul 06 '16 at 07:05
0

EDITED after looking on the code Qt 5.7,5.6,5.5 and reading docs.

As an answer is already accepted, I would just add some thoughts here as it's too long for comments.

Keep things short - an answer you accepted is wrong..

There are two sides of the story. And as SO answers often taken 'as it is as long as they work' I'd like to explain myself...

If you look on a code provided - there is nothing wrong with it. All objects are properly stack allocated and should be destroyed automatically.

Point is that QtSerial uses deleteLater() and then a question - how to delete those allocations properly.

If any module/object/code uses deleteLater() it requires an event loop, if deleteLater() called on a thread without event loop, object will be deleted after thread is terminated. As long as there is no event loop running for code above, processEvents will no work.. actually processEvents() is not something which is used for this, because a whole idea to return from the context which is called deleteLater() and have a next run, and that's checked in the Qt Source Code, so calling processEvent() straight after without incrementing loop count will do nothing at all, that's why answer you accepted is totally wrong.

Conclusion:

If any object requires event loop running it should be EXPLICITELY stated in the documentation as there is nothing wrong in using QIODevice in sync mode outside event loop.

So at my opinion,point is - its a bug in the QT Serial itself which I suggest you report.

In general it's really wrong practice for Qt to run never-ending loops.. It's much much better and cleaner to use QObject Worker tactic which is pushed to the thread, have proper even loop running etc.

For small 'threaded' tasks it's much better to use QtConcurrent.

Proper Workaround:

you will have a thread with properly running event loop and a timer firing at 20ms to do your things

// main thread:
class Worker: public QObject {
    public:
       Worker();

    public slots:

       onInit() {
          // initialize everything
          startTimer(20);
       }

    protected:

       void timerEvent(..) {
          // do your things every 20ms
       }   
  }

  ...

  QThread * pWorkerThread = new QThread();
  pWorkerThread->setObjectName(QString("Serial"));

  Worker * pWorker = new Worker();
  Worker->setObjectName(QString("Common Storage Impl"));
  Worker->moveToThread(WorkerThread);

  connect(pWorkerThread, SIGNAL(started()), pWorker, SLOT(onInit()));
  connect(pWorkerThread, SIGNAL(finished()), pWorker, SLOT(deleteLater()));
  connect(pWorkerThread, SIGNAL(finished()), pWorkerThread, SLOT(deleteLater()));

  pWorkerThread->start();

  ...
evilruff
  • 3,947
  • 1
  • 15
  • 27
  • I agree, but what might be the side effects you are talking about? Can you give some example, so I can avoid them while I make this as a temporary fix. – Nyaruko Jul 06 '16 at 06:43
  • @evilruff my comment about deleteLater might have been wrong, my answer doesn't mention it. In fact, I don't think deleteLater gets called anywhere but in the destructor of `QSerialPort` which obviously won't accumulate in this case. – PeterT Jul 06 '16 at 08:27
  • Well, I changed my answer.. I think it's deleteLater() as I briefly had a look, Serial uses QTimer which might easily use deleteLater(), but I would still address this issue to Qt Bugtracker.. anyway processEvents is not something will which destroy deleteLaters() at all.. – evilruff Jul 06 '16 at 08:44
  • But the thing is, after I have added the processEvents call then there is no more leak? – Nyaruko Jul 06 '16 at 11:14
  • As I wrote, it's very very Qt version specific.. I tried to explain why it's wrong way to go, but it's up to you to use it or not. – evilruff Jul 06 '16 at 12:42
  • I disagree. It is implied that any `QIODevice` or really any class that issues asynchronous signals has to originate them in the event loop. There's no other way to emit signals safely otherwise! Just think about it. Furthermore, the `waitFor` methods promote spaghetti code. Real world isn't synchronous, many things can happen. Just because you wait for the data at some point in your code doesn't mean that e.g. the device might not vanish at that point - and what do you do then? And so on. – Kuba hasn't forgotten Monica Jul 07 '16 at 16:05
  • I agree that in most of cases we do you signals etc BUT I think you can imagine scenarios, where for example we have a thread which serves hundreds of connections and then we have many threads, and in this case having a fixed timer for example to serve sockets in a loop for receiving and sending is not really bad design, nginx works like this for example.. I mean QIODevice or QTcpSocket at the end of the day are just wrappers around certain API, I agree they are supposed to be used async, but sometimes I wrote code where sync way was much more cleaner readable and perform much better. – evilruff Jul 07 '16 at 16:10
  • For example it's much easier to implement traffic shaping in a design I explained above, so in general I agree - yes in 99% of a cases async is right way to go with event loop, BUT, it doesnt mean it should eat memory being used in a sync mode, otherwise it should be clearly stated in the documentation – evilruff Jul 07 '16 at 16:11
  • @KubaOber, I am lost here. So we should never use Qt class inside a thread without event loop? But there are plenty of examples from Qt's official documentation teach us how to do that. – Nyaruko Jul 07 '16 at 16:35
  • No that's not what Kuba means.. what he is saying is that QIODevice based classes designed to be used with async environments with event loop, but my five penny's is that it's not a paradigm.. you can do what you think is convenient and practical.. – evilruff Jul 07 '16 at 16:42
  • Thanks, though they are designed for async, there are qt's official example teaches you how to do in in sync mode... Like this:http://doc.qt.io/qt-5/qtserialport-blockingmaster-example.html And this example itself... is leaking – Nyaruko Jul 07 '16 at 16:46
  • exactly.. take it easy, just pay attention what's really going on inside and it will be fine – evilruff Jul 07 '16 at 16:47