I am trying to close a serial port opened using the QSerialPort library but it hangs more than half the time.
I am developing a multi-threaded app, with one thread responsible for UI and the other for serial communication. I am using the QThread wrapper class.
void CommThread::run()
{
serial = new QSerialPort();
serial->setPortName(portname);
serial->setBaudRate(QSerialPort::Baud115200);
if(!serial->open(QIODevice::ReadWrite)){
qDebug() << "Error opening Serial port within thread";
quit = true;
return;
}else{
/// \todo handle this exception more gracefully
}
/// Start our reading loop
/// While CommThread::disconnect is not called, this loop will run
while(!quit){
comm_mutex->lock();
/// If CommThread::disconnect() is called send DISCONNECT Packet
if(dconnect){
// Signal device to disconnect so that it can suspend USB CDC transmission of data
qDebug() << "Entering disconnect sequence";
serial->write(data);
serial->flush();
break;
}
/// No write or disconnect requested
/// Read incoming data from port
if(serial->waitForReadyRead(-1)){
if(serial->canReadLine()){
// Read stuff here
}
}
// Transform the stuff read here
comm_mutex->lock()
// Do something to a shared data structure
// emit signal to main thread that data is ready
comm_mutex->unlock();
}
comm_mutex->unlock();
// Thread is exiting, clean up resources it created
qDebug() << "Thread ID" << QThread::currentThreadId();
qDebug() << "Thread:: Closing and then deleting the serial port";
qDebug() << "Lets check the error string" << serial->errorString();
delete comm_mutex;
serial->close();
qDebug() << "Thread:: Port closed";
delete serial;
qDebug() << "Thread:: Serial deleted";
delete img;
qDebug() << "Thread:: Image deleted";
qDebug() << "Thread:: Serial port and img memory deleted";
quit = true;
}
The problem is when the UI thread sets the dconnect variable to true and proceeds to delete the communication thread it gets stuck in the destructor of the communication thread which looks like this:
CommThread::~CommThread()
{
qDebug() << "Destructor waiting for thread to stop";
QThread::wait();
qDebug() << "Destuctor Commthread ID" << QThread::currentThreadId();
qDebug() << "Commthread wrapper exiting";
}
2 out of three times, the communication thread hangs at the serial-close()
line, causing the UI thread to hang at the QThread::wait()
line in the destructor. Needless to say this results in a frozen UI and if closed, the entire application remains in memory until killed by the task manager. Given a few minutes the call to serial::close() will finally return; what I would like to know is what's wrong and how can I best avoid a hanging UI?
I have looked into the code of QSerialPort and I can't see anything manifestly wrong. If I call serial->errorCode()
I get the UknownError string but that happens even when the port closes with no hangups.
EDIT: This NEVER happens in the debugger. The SerialPort always closes immediately and the destructor sails through with no hangups on QThread::wait()
EDIT: I am certain it is the serial->close() which is hanging because I can see the qDebug() statement being printed just before it hangs for several seconds or minutes).
The device stops transmitting because in the dconnect switch, a disconnect packet is sent and a LED on the device turns green.
Several things:
You can certainly simply leak the port if it doesn't close soon enough.
You should perform a graceful exit where the UI is responsive and the thread shutdown is attempted with a timeout.
You should use smart pointers and other RAII techniques to manage resources. This is C++, not C. Ideally, store things by value, not through a pointer.
You must not block in the of the sections where you modify the shared data structure(s) under a lock.
You should be notifying of changes to the data structure (perhaps you do). How can other code depend on such changes without polling otherwise? It can't, and polling is horrible for performance.
QThread
offersrequestInterruption
andisInterruptionRequested
for code that reimplementsrun
without an event loop. Use it, don't roll your wonquit
flags.Your code would be much simpler if you had used a
QObject
directly.At the very minimum, we want a UI that won't block on a worker thread being shut down. We start with a thread implementation that has the functionality needed to support such a UI.
It's a bit of a lie that
Thread
is-aQThread
since we cannot use some of the base class's members on it, thus breaking the LSP. Ideally,Thread
should be aQObject
, and only internally contain aQThread
.We then implement a dummy thread that takes its time to terminate, and can optionally get stuck permanently, just like your code sometime does (although it doesn't have to).
We then need a class that can link up worker threads and UI close requests. It installs itself as an event filter on the main window, and delays its closing until all threads have terminated.
Finally, we have a simple UI that allows us to control all this and see that it works. At no point is the UI unresponsive or blocked.
Given the
Thread
class, and following advice above (other than point 7), yourrun()
should look roughly as follows:Of course, this is a truly horrible style that unnecessarily spins the loop waiting for things to happen, etc. Instead, the trivial way to approach such issues is to have a
QObject
with a thread-safe interface that can be moved to a worker thread.First, a curiously recurring helper; see this question for details.
We derive from
QObject
and usepostTo
to execute functors from our thread's event loop.Let's observe that any
QIODevice
automatically closes on destruction. Thus all we need to do to close the port is to destruct it, in the desired worker thread so that the long operation doesn't block the UI.Thus, we really want the object (and its port) to be deleted in its thread (or leak). This is simply accomplished by connecting
Thread::stopping
to the object'sdeleteLater
slot. There, the port closing can take as much time as needed - theThread
will terminate its execution if it times out. All the while the UI remains responsive.