Calling QLabel::setText causes a write acces violation
-
Hi all,
I have recently started on my first Qt application and I found myself stuck at the following issue:
My application will start correctly and run for a (seemingly) random time before I get the following exception:
bq. The inferior stopped because it triggered an exception.
Stopped in thread 0 by: Exception at 0x7fee38174c1, code: 0xc0000005: write access violation at: 0x1, flags=0x0I narrowed down the issue to a text update of a QLabel in my mainwindow.
I am trying to update a text label which displays the frequency of another worker thread that's communicating with a serial device.My code is set up in the following manner:
My mainwindow has a slot and listens for a signal emitted from another part of my program that fires when the update frequency of my serial device has changed. In this slot a signal is fired to active the setText of my QLabel in a statusbar in the mainwindow. (See below)
@class MainWindow : public QMainWindow
{
Q_OBJECTpublic:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();private slots:
void on_updateRateChanged(float Rate);signals:
void UpdateHearbeatLabel(const QString& Text);private:
Ui::MainWindow * ui;
QLabel* pHeartbeatFreqLabel;
};@@MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, pHeartbeatFreqLabel(new QLabel(this))
{
ui->setupUi(this);
ui->centralWidget->hide();
ui->statusBar->addPermanentWidget(pHeartbeatFreqLabel);qDebug() << "MainWindow created in thread: " << QThread::currentThreadId();
pHeartbeatFreqLabel->setText("Update rate: 0.0 Hz");
pHeartbeatFreqLabel->setStyleSheet("QLabel { color : red; }");connect(this, &MainWindow::UpdateHearbeatLabel, pHeartbeatFreqLabel, &QLabel::setText);
}void MainWindow::on_updateRateChanged(float Rate)
{
QString s = "Update rate: ";
s += QString::number(Rate, 'f', 1);
s += " Hz";qDebug() << "settext in thread: " << QThread::currentThreadId();
emit UpdateHearbeatLabel(s);
}@As visible in the code I print out the current thread id's of the mainwindow contructor and the on_updateRateChanged slot. They both print out the same id's (i.e. the main thread).
The application run's fine when the UpdateHeartbeatLabel signal is not emitted. But when it is it crashes after a (seemingly random amount of emissions). The debugger then shows the following disassembly:
@ Qt5Guid!QRegion::QRegion [c:\work\build\qt5_workdir\w\s\qtbase\src\gui\painting\qregion.cpp "@" 3799]:
0x7fee44e7450 mov dword ptr [rsp+18h],r8d
0x7fee44e7455 <+0x0005> mov qword ptr [rsp+10h],rdx
0x7fee44e745a <+0x000a> mov qword ptr [rsp+8],rcx
0x7fee44e745f <+0x000f> sub rsp,0B8h
0x7fee44e7466 <+0x0016> mov rcx,qword ptr [rsp+0C8h]
0x7fee44e746e <+0x001e> call qword ptr [Qt5Guid!imp?isEmptyQRectQEBA_NXZ (000007fee4875a10)] 0x7fee44e7474 <+0x0024> movzx eax,al 0x7fee44e7477 <+0x0027> test eax,eax 0x7fee44e7479 <+0x0029> je Qt5Guid!QRegion::QRegion+0x55 (000007fe
e44e74a5)
0x7fee44e747b <+0x002b> mov rax,qword ptr [rsp+0C0h]
0x7fee44e7483 <+0x0033> lea rcx,[Qt5Guid!QRegion::shared_empty (000007fee48276f0)] 0x7fee44e748a <+0x003a> mov qword ptr [rax],rcx 0x7fee44e748d <+0x003d> mov rax,qword ptr [rsp+0C0h] 0x7fee44e7495 <+0x0045> mov rax,qword ptr [rax] 0x7fee44e7498 <+0x0048> mov rcx,rax 0x7fee44e749b <+0x004b> call Qt5Guid!ILT+106570(?ref?$QBasicAtomicIntegerHQEAA_NXZ) (000007fe
e407b04f)
0x7fee44e74a0 <+0x0050> jmp Qt5Guid!QRegion::QRegion+0x213 (000007fee44e7663) 0x7fee44e74a5 <+0x0055> mov ecx,10h 0x7fee44e74aa <+0x005a> call Qt5Guid!operator new (000007fe
e4639a7e)
0x7fee44e74af <+0x005f> mov qword ptr [rsp+58h],rax
0x7fee44e74b4 <+0x0064> mov rax,qword ptr [rsp+0C0h]
0x7fee44e74bc <+0x006c> mov rcx,qword ptr [rsp+58h]
0x7fee44e74c1 <+0x0071> mov qword ptr [rax],rcx@At the last line (24) the application get's a write access violation. I have no idea if I have stumbled upon a Qt bug or whether I am simply doing something wrong.
I also tried calling the setText method directly:
@pHeartbeatFreqLabel->setText(s);@
This did not change the behavior.
Qt Version used:
Qt Creator 3.2.2 (opensource)
Qt 5.3.2
Compiler: Qt 5.3 MSVC2013 32bit 5.3.2.0Has anybody got an idea what causes this behavior? Help would be greatly appreciated.
Ron
P.S. I left out some non relevant code to simplify my question.
-
What is the stack backtrace leading to the crash? Why do you think threads have anything to do with this? Does anything in your program call QStatusBar::removeWidget()?
I am not sure why you use a signal/slot connection to set the label's text when you could simply call setText() directly.
-
[quote author="ChrisW67" date="1414096153"]What is the stack backtrace leading to the crash? Why do you think threads have anything to do with this? Does anything in your program call QStatusBar::removeWidget()?
I am not sure why you use a signal/slot connection to set the label's text when you could simply call setText() directly.[/quote]
You're right just calling the setText() function is simpler. I tried that at first and this signal/slot connection was just an attempt to fix the problem. Sadly it didn't.
As for the threads, I recently had some issues with my worker thread and was just trying to exclude any multi-threaded issues to narrow down the problem.
Nothing in my code calls QStatusBar::removeWidget().
The full stack trace can be found "here.":https://dl.dropboxusercontent.com/u/6492315/backtrace.txt
The debugger stops in thread 0 which has not much backtracing
@. 0 Id: 680.2250 Suspend: 1 Teb: 000007ff
fffde000 Unfrozen Child-SP RetAddr Call Site 00000000
002ca510 0000000000000000 Qt5Guid!QRegion::QRegion(class QRect * r = 0x00000000
002ca6d0, QRegion::RegionType t = Rectangle (0n0))+0x71 [c:\work\build\qt5_workdir\w\s\qtbase\src\gui\painting\qregion.cpp "@" 3804]
@ -
Hi,
It would help to see the code of your worker thread. If you are emitting a signal of MainWindow from another thread, then it sould be move to that thread to have the right thread affinity (see "QObject::moveToThread()":http://qt-project.org/doc/qt-5/qobject.html#moveToThread). Otherwise, the signal will be processed directly and may cause concurrent accesses.
You said that MainWindow::on_updateRateChanged() is called in the main thread, so the affinity looks right, unless your thread initialization is wrong. Again, it would help to see the code.
-
[quote author="tilsitt" date="1414136773"]Hi,
It would help to see the code of your worker thread. If you are emitting a signal of MainWindow from another thread, then it sould be move to that thread to have the right thread affinity {...}[/quote]
It would seem it has indeed something to do with my worker thread. I come to this conclusion since the application only crashed when my communication handler class emits a sendHeartbeat signal to my worker thread.
My application is set up in the following manner:
CCommHandler class, provides a way of easily sending write/read and heartbeat serial messages to my serial device. It has CCommWorker as a member which is pushed onto a separate thread for blocking calls to the serial device. (see below)
@class CCommHandler : public QObject
{
Q_OBJECTpublic:
CCommHandler(void);
~CCommHandler(void);
void WriteRegister(uint8_t Address, uint32_t Data);
void ReadRegister(uint8_t Address, uint32_t* Data);public slots:
void on_refreshComPorts(void);
void on_selectedComPortChanged(QString Port);
void on_selectedBaudrateChanged(QString Baudrate);
void on_connect(void);
void on_disconnect(void);private slots:
void on_frameReceived(RSMC::EFrameResult Result, uint32_t Data);
void on_connected(void);
void on_disconnected(void);
void on_heartbeatUpdate(void);
void on_controllerConnected(unsigned int controllerNum);
void on_controllerDisconnected(unsigned int controllerNum);signals:
void ComPortsChanged(QStringList CommPorts);
void Connected(void);
void Disconnected(void);
void FrameReceived(RSMC::EFrameResult Result, uint32_t Data);void SetComPort(QString Port);
void SetBaudRate(QString Baud);
void Connect(void);
void Disconnect(void);
void SendHeartBeatSignal(float Roll, float Pitch, float Yaw, float Throttle, uint32_t* Data);
void WriteRegisterSignal(uint8_t Address, uint32_t Data);
void ReadRegisterSignal(uint8_t Address, uint32_t* Data);void ControllerConnected(unsigned int controllerNum);
void ControllerDisconected(unsigned int controllerNum);
void HeartBeatFrequencyChanged(float Frequency);protected:
void run();private:
bool mConnected;
float mPrevFreq;
QStringList mCommPorts;
QThread mWorkerThread;
QTimer mHeartbeatTimer;
CCommWorker* pCommWorker;
SimpleXbox360Controller* pController;
SimpleXbox360Controller::InputState mControllerInputState;
};@@CCommHandler::CCommHandler(void)
: mConnected(false)
, mPrevFreq(0.0f)
, pCommWorker(new CCommWorker)
, pController(new SimpleXbox360Controller(0,7849,8689,30,this))
{
mHeartbeatTimer.setTimerType(Qt::PreciseTimer);connect(&mWorkerThread, &QThread::finished, pCommWorker, &QObject::deleteLater);
connect(&mHeartbeatTimer, &QTimer::timeout, this, &CCommHandler::on_heartbeatUpdate);connect(pCommWorker, &CCommWorker::Connected, this, &CCommHandler::on_connected);
connect(pCommWorker, &CCommWorker::Disconnected, this, &CCommHandler::on_disconnected);
connect(pController, &SimpleXbox360Controller::controllerConnected, this, &CCommHandler::on_controllerConnected);
connect(pController, &SimpleXbox360Controller::controllerDisconnected, this, &CCommHandler::on_controllerDisconnected);connect(this, &CCommHandler::Connect, pCommWorker, &CCommWorker::ConnectSerial);
connect(this, &CCommHandler::Disconnect, pCommWorker, &CCommWorker::DisconnectSerial);
connect(this, &CCommHandler::SetBaudRate, pCommWorker, &CCommWorker::SetBaudRate);
connect(this, &CCommHandler::SetComPort, pCommWorker, &CCommWorker::SetComPort);connect(this, &CCommHandler::SendHeartBeatSignal, pCommWorker, &CCommWorker::SendHeartBeat);
connect(this, &CCommHandler::WriteRegisterSignal, pCommWorker, &CCommWorker::WriteRegister);
connect(this, &CCommHandler::ReadRegisterSignal, pCommWorker, &CCommWorker::ReadRegister);
connect(pCommWorker, &CCommWorker::FrameReceived, this, &CCommHandler::on_frameReceived);pCommWorker->moveToThread(&mWorkerThread);
mWorkerThread.start(QThread::TimeCriticalPriority);
mHeartbeatTimer.start(HeartbeatInterval);
}CCommHandler::~CCommHandler(void)
{
mHeartbeatTimer.stop();
mWorkerThread.quit();
mWorkerThread.wait();
delete pController;
delete pCommWorker;
}
void CCommHandler::on_controllerConnected(unsigned int controllerNum)
{
emit ControllerConnected(controllerNum);
}void CCommHandler::on_controllerDisconnected(unsigned int controllerNum)
{
emit ControllerDisconected(controllerNum);
}void CCommHandler::on_heartbeatUpdate(void)
{
pController->update();
if(mConnected)
{
uint32_t Data[3]; // Dummy
mControllerInputState = pController->getCurrentState();
float Roll = mControllerInputState.leftThumbX;
float Pitch = mControllerInputState.leftThumbY;
float Yaw = mControllerInputState.rightThumbX;
float Throttle = mControllerInputState.rightTrigger;
emit SendHeartBeatSignal(Roll, Pitch, Yaw, Throttle, Data); // When this line is commented the application does not crash anymore
}
float currFreq = pCommWorker->GetHeartBeatFrequency();
if(mPrevFreq - currFreq >= 1.0f ||
mPrevFreq - currFreq <= -1.0f)
{
mPrevFreq = currFreq;
qDebug() << "Emit thread id: " << QThread::currentThreadId();
emit HeartBeatFrequencyChanged(currFreq);
}}@
-
Commenting line 61 of CComHandler.cpp let's the problem disappear.
The signal on that line is connected to the CommWorker which consists out of the following:CComWorker.h
@class CCommWorker : public QObject
{Q_OBJECTpublic:
CCommWorker(void);
~CCommWorker(void);
float GetHeartBeatFrequency(void);public slots:
void SendHeartBeat(float Roll, float Pitch, float Yaw, float Throttle, uint32_t* Data);
void WriteRegister(uint8_t Address, uint32_t Data);
void ReadRegister(uint8_t Address, uint32_t* Data);
void SetComPort(QString Port);
void SetBaudRate(QString Baud);
void ConnectSerial(void);
void DisconnectSerial(void);signals:
void FrameReceived(RSMC::EFrameResult Result, uint32_t Data);
void Connected(void);
void Disconnected(void);private:
QSerialPort* pSerialPort;
TCallback<CCommWorker> mCallbackTx;
TCallback<CCommWorker> mCallbackRx;
TCallback<CCommWorker> mCallbackTime;
RSMC::CMultiFrameHandler* pMultiFrameHandler;
RSMC::SMultiFrameData mSendFrame;
RSMC::SMultiFrameData mRecvFrame;
QSettings mSettings;
QTime mTime;
QMutex mMutex;
QMutex mMutexLocalVar;
float mHeartbeatUpdateFrequency;
uint8_t Rx(void* Param1, void* Param2);
uint8_t Tx(void* Param1, void* Param2);
uint8_t GetTime(void* Param1, void* Param2);
void PrintSerialPortError(QSerialPort::SerialPortError Error);
};@CComWorker.cpp
@CCommWorker::CCommWorker(void)
: pSerialPort(new QSerialPort(this))
, mHeartbeatUpdateFrequency(0.0f)
{
pSerialPort->setBaudRate(EBaudRate9600);
mCallbackTx.SetCallback(this, &CCommWorker::Tx);
mCallbackRx.SetCallback(this, &CCommWorker::Rx);
mCallbackTime.SetCallback(this, &CCommWorker::GetTime);
pMultiFrameHandler = new RSMC::CMultiFrameHandler(true, &mCallbackRx, &mCallbackTx, &mCallbackTime);
}CCommWorker::~CCommWorker(void)
{
delete pSerialPort;
}uint8_t CCommWorker::Rx(void* Param1, void* Param2)
{
qApp->processEvents();
char* data = reinterpret_cast<char*>(Param1);
uint8_t length = reinterpret_cast<uint8_t>(Param2);
uint8_t bytesAvailable = pSerialPort->bytesAvailable();
if(length > bytesAvailable)
{
length = bytesAvailable;
}
uint8_t bytesRead = pSerialPort->read(data, length); // TODO: Check for available bytes, open port or does -1 catch this?if(bytesRead == -1) // An error occured
{
return 0;
}
else
{
return bytesRead;
}
}uint8_t CCommWorker::Tx(void* Param1, void* Param2)
{
qApp->processEvents();
char* data = reinterpret_cast<char*>(Param1);
uint8_t length = reinterpret_cast<uint8_t>(Param2);uint8_t bytesWritten = pSerialPort->write(data, length); // TODO: Check for open serial port needed or does -1 catch this?
pSerialPort->flush();
pSerialPort->waitForBytesWritten(-1);if(bytesWritten == -1) // An error occured
{
return 0;
}
else
{
return bytesWritten;
}
}uint8_t CCommWorker::GetTime(void* Param1, void*)
{
// TODO: implement
return 0;
}float CCommWorker::GetHeartBeatFrequency(void)
{
mMutexLocalVar.lock();
float result = mHeartbeatUpdateFrequency;
mMutexLocalVar.unlock();return result;
}void CCommWorker::SendHeartBeat(float Roll, float Pitch, float Yaw, float Throttle, uint32_t *Data)
{
if(mMutex.tryLock() == false)
{
return;
}
mSendFrame.commandHeader.Bit.cmdType = static_cast<uint8_t>(RSMC::ECmdTypeHb);
mSendFrame.commandHeader.Bit.replyWanted = 1;
union UData
{
float f;
uint32_t i;
};
uint32_t sendData[4];
UData d;
d.f = Roll;
sendData[0] = d.i;
d.f = Pitch;
sendData[1] = d.i;
d.f = Yaw;
sendData[2] = d.i;
d.f = Throttle;
sendData[3] = d.i;mSendFrame.data = sendData;
mSendFrame.dataLength = sizeof(sendData); // TODO remove this, not needed/used
RSMC::EFrameResult sendResult = pMultiFrameHandler->Push(mSendFrame);
while( sendResult == RSMC::EFrameResultPending)
{
sendResult = pMultiFrameHandler->Push(mSendFrame);
}if(sendResult != RSMC::EFrameResultOk)
{
emit FrameReceived(sendResult, mSendFrame.data[0]);
mMutex.unlock();
return;
}
mRecvFrame.data = Data;
mRecvFrame.dataLength = sizeof(Data);RSMC::EFrameResult recvResult = pMultiFrameHandler->Pull(&mRecvFrame);
while( recvResult == RSMC::EFrameResultPending)
{
recvResult = pMultiFrameHandler->Pull(&mRecvFrame);
}
emit FrameReceived(recvResult, mRecvFrame.data[0]);
mMutexLocalVar.lock();
mHeartbeatUpdateFrequency = 1000.0f/static_cast<float>(mTime.elapsed());
mMutexLocalVar.unlock();
mTime.restart();
mMutex.unlock();
}@ -
Hi,
@
if(sendResult != RSMC::EFrameResultOk)
{
emit FrameReceived(sendResult, mSendFrame.data[0]);
mMutex.unlock();
return;
}
@IIRC this might be the real problem.
You should rather take the data you need for your signal, unlock the mutex and only then emit your signal.Hope it helps
-
[quote author="SGaist" date="1414184112"]Hi,
IIRC this might be the real problem.
You should rather take the data you need for your signal, unlock the mutex and only then emit your signal.Hope it helps[/quote]
SGaist, thanks for your reply.
If I understand correctly you are saying I should switch line 3 and 4?
I tried this and it did not solve my problem. The code you quoted was never called anyway. But to be sure I commented the 2 lines where FrameReceived is emitted. It had no effect on my problem the application still crashed after some time. -
Not completely, with mSendFrame.data[0] you are still accessing a variable that should be protected.
After some time ? Do you mean that your application is running for a while and only then crash ?
-
[quote author="SGaist" date="1414275985"]
After some time ? Do you mean that your application is running for a while and only then crash ?[/quote]Yes my application runs fine without crashing at first. But when my program is connected to my Serial device and (more importantly )begins emitting the "SendHeartBeatSignal" the application still works for a random amount of time and then crashes.
-
Some race condition somewhere…
Tricky to debug. Maybe running your application through GammaRay might help find what goes wrong.
-
Yes probably a race condition.
I never heard of GammaRay I assume you mean "this":http://www.kdab.com/kdab-products/gammaray/I will give it a try when I have some time, seems that this bug will take quite some digging to find.
-
That's the one indeed.
Multithread bugs are pretty hard to find.
One thing you can also do, is to first ensure that everything works fine without any thread (QSerialPort works asynchronously) and only then move to the multithread paradigm.
-
That would be a good approach, I will take a look at it this weekend when I have some time.