Crash on lambda destructor (QString reference count)
-
I am having a very strange crash when assiging
nullptr
to a lambda. It appears that before assiging thenullptr
, the last lambda value is destroyed. The last lambda value captured a couple ofQStrings
and it seems that Qt crashes when is trying to decrease the counter of theQString
instances.I don't know if I am doing something ilegal, or if this is a known issue, I could not find any info online. I attach some screenshots of the callstack.
Environment:
- Qt 5.6.3
- CentOS 7
- GCC 4.8.5
This is where the
nullptr
is assigned (stack number10
):This is where the last lambda value is destroyed and the crash follows (stack number
6
): -
@VRonin sure, it is
std::function<void(void)> m_funcUpdatePartnerHost
-
@VRonin sure, it is
std::function<void(void)> m_funcUpdatePartnerHost
What is
strSocketHost
andstrSocketPort
? What ism_generalPartner
and what does.fetch()
do/return? -
Sorry,
strSocketHost
andstrSocketPort
areQString
instances (captured by the two lambdas shown in the screenshot.
To explain further;-
The
.done([this, strSocketHost, strSocketPort]() { ... })
callback gets called after fetching some data from the network. -
During the execution of the
.done
callback, them_funcUpdatePartnerhost
is assigned to[this, strSocketHost, strSocketPort]() { ... }
as seen in the screenshot. -
Later, when another event occurs, the
m_funcUpdatePartnerhost
is assigned tonullptr
and this is when the crash occurs.
As I understand, the lambdas capture the
strSocketHost
andstrSocketPort
(QString
) by value, therefore Qt should increase the instance count of those strings, so they do not get deleted.Then when the
m_funcUpdatePartnerhost
is assigned tonullptr
, the old lambda ([this, strSocketHost, strSocketPort]() { ... }
) get deleted, therefore theQString
destructor reduces the instance count, and this is when the crash occurs.Hope I made the scenario clearer.
-
-
Nice example on how to abuse lambdas so nobody understands what's going on there :)
Use valgrind to see where the object get's deleted. Simplify the code until either we can compile and test it or it no longer crashes. -
Sorry,
strSocketHost
andstrSocketPort
areQString
instances (captured by the two lambdas shown in the screenshot.
To explain further;-
The
.done([this, strSocketHost, strSocketPort]() { ... })
callback gets called after fetching some data from the network. -
During the execution of the
.done
callback, them_funcUpdatePartnerhost
is assigned to[this, strSocketHost, strSocketPort]() { ... }
as seen in the screenshot. -
Later, when another event occurs, the
m_funcUpdatePartnerhost
is assigned tonullptr
and this is when the crash occurs.
As I understand, the lambdas capture the
strSocketHost
andstrSocketPort
(QString
) by value, therefore Qt should increase the instance count of those strings, so they do not get deleted.Then when the
m_funcUpdatePartnerhost
is assigned tonullptr
, the old lambda ([this, strSocketHost, strSocketPort]() { ... }
) get deleted, therefore theQString
destructor reduces the instance count, and this is when the crash occurs.Hope I made the scenario clearer.
@juangburgos said in Crash on lambda destructor (QString reference count):
The .done(this, strSocketHost, strSocketPort { ... }) callback gets called after fetching some data from the network.
.done()
is a method, just as is.fetch()
, I'll ask the same thing again, what do they return, at the very least provide the declarations of the relevant types.Later, when another event occurs, the m_funcUpdatePartnerhost is assigned to nullptr and this is when the crash occurs.
The logic is wrong. You're introducing a state which is shared between all events, you're begging for trouble.
As I understand, the lambdas capture the strSocketHost and strSocketPort (QString) by value, therefore Qt should increase the instance count of those strings, so they do not get deleted.
This doesn't guard against you smashing the stack (or heap).
Take note:
@Christian-Ehrlicher said in Crash on lambda destructor (QString reference count):
Nice example on how to abuse lambdas so nobody understands what's going on there :)
Use valgrind to see where the object get's deleted. Simplify the code until either we can compile and test it or it no longer crashes. -
-
I tried this simplified case but can't reproduce the problem (in Qt 5.15). Can you:
- try the example below on your configuration and see if it crashes (Qt 5.6 is ancient and I can't fire it up easily on my end)?
- suggest how to make it more similar to your case to the point where we can test the crash?
#include <QCoreApplication> #include <QDebug> #include <QTimer> class TestLambda : public QObject{ public: explicit TestLambda(QObject* parent = nullptr) :QObject(parent) { QString strSocketHost(QLatin1String("localhost")); QString strSocketPort(QLatin1String("8080")); m_generalPartner = [this,strSocketHost,strSocketPort](){ qDebug() << strSocketHost << strSocketPort; m_funcUpdatePartnerHost= [this,strSocketHost,strSocketPort](){ qDebug() << strSocketHost << strSocketPort; QTimer::singleShot(1000,this,&TestLambda::setFunctNull); }; QTimer::singleShot(1000,this,m_funcUpdatePartnerHost); }; } void setFunctNull(){ qDebug("Setting to NULL"); m_funcUpdatePartnerHost=nullptr; QTimer::singleShot(1000,qApp,&QCoreApplication::quit); } void start(){ m_generalPartner(); } std::function<void(void)> m_funcUpdatePartnerHost; std::function<void(void)> m_generalPartner; }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); TestLambda testCrash; testCrash.start(); return app.exec(); }
-
I tried this simplified case but can't reproduce the problem (in Qt 5.15). Can you:
- try the example below on your configuration and see if it crashes (Qt 5.6 is ancient and I can't fire it up easily on my end)?
- suggest how to make it more similar to your case to the point where we can test the crash?
#include <QCoreApplication> #include <QDebug> #include <QTimer> class TestLambda : public QObject{ public: explicit TestLambda(QObject* parent = nullptr) :QObject(parent) { QString strSocketHost(QLatin1String("localhost")); QString strSocketPort(QLatin1String("8080")); m_generalPartner = [this,strSocketHost,strSocketPort](){ qDebug() << strSocketHost << strSocketPort; m_funcUpdatePartnerHost= [this,strSocketHost,strSocketPort](){ qDebug() << strSocketHost << strSocketPort; QTimer::singleShot(1000,this,&TestLambda::setFunctNull); }; QTimer::singleShot(1000,this,m_funcUpdatePartnerHost); }; } void setFunctNull(){ qDebug("Setting to NULL"); m_funcUpdatePartnerHost=nullptr; QTimer::singleShot(1000,qApp,&QCoreApplication::quit); } void start(){ m_generalPartner(); } std::function<void(void)> m_funcUpdatePartnerHost; std::function<void(void)> m_generalPartner; }; int main(int argc, char *argv[]) { QCoreApplication app(argc,argv); TestLambda testCrash; testCrash.start(); return app.exec(); }
@VRonin said in Crash on lambda destructor (QString reference count):
suggest how to make it more similar to your case to the point where we can test the crash?
Yes, please. I've encountered a bug in the code generation (the capture was generating wrong assembly) with MSVC (it was an old version and was fixed in the update), but never with gcc ...
-
@VRonin said in Crash on lambda destructor (QString reference count):
suggest how to make it more similar to your case to the point where we can test the crash?
Yes, please. I've encountered a bug in the code generation (the capture was generating wrong assembly) with MSVC (it was an old version and was fixed in the update), but never with gcc ...
I wish I could reproduce it easily. I have indeed tried in multiple ways. This is a bug that happens rarely in production. The code shown is single threaded.
The stack you see is actually from a dump file. I am using QDeferred for the
.done
method.Will keep trying to minimally reproduce.