QTimer doesn't stop on calling stop()
-
I am using a
QTimer
to scan aQVector
of probes every second and remove any probes that have expired. After a certain point, I need to stop the timer but I keep on receiving timeout events even after callingstop()
on theQTimer
object, which eventually leads to a crash.class MyObject : public QObject { Q_OBJECT QVector<Probe> m_pending; QList<Probe> m_done; QTimer m_timer; public: MyObject(QObject* p): QObject(p) { connect(&m_timerr, &QTimer::timeout, this, &MyObject::timeoutFired, Qt::DirectConnection); m_timer.setInterval(1000); m_timer.start(); } void timeoutFired() { qDebug() << "[timeout] timeoutFired"; if (!m_timer.isActive()) return; auto pivot = std::partition(m_pending.begin(), m_pending.end(), [this](const Probe& probe) { return !probe.expired; }); for (auto it = pivot; it != m_pending.end(); ++it) { process(*it); } m_pending.resize(std::distance(m_pending.begin(), pivot)); } void someFunction() { ... m_timer.stop(); ... } }
Following is the stack tracing of the crashing process
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x00007ff805146c58 libc++abi.dylib`__cxa_throw * frame #1: 0x000000010c7969fc QtCore`qBadAlloc() at qglobal.cpp:3356:5 [opt] frame #2: 0x000000010c7e60d5 QtCore`QListData::detach_grow(this=0x00007f9f9a41a450, idx=0x000000030597af54, num=<unavailable>) at qlist.cpp:97:5 [opt] frame #3: 0x0000000100c05c73 MyProgram`QList<Probe>::detach_helper_grow(this=0x00007f9f9a41a450, i=2147483647, c=1) at qlist.h:803:28 frame #4: 0x0000000100bf1ed0 MyProgram`QList<Probe>::append(this=0x00007f9f9a41a450, t=0x000000030597b0e0) at qlist.h:623:19 frame #5: 0x0000000100bf22e4 MyProgram`MyObject::process(this=0x00007f9f9a005950, Probe=0x00000001239999e) at MyProgram.cpp:2216:5 frame #6: 0x0000000100bf01b5 MyProgram`MyObject::timeoutFired(this=0x00007f9f9a005950) at MyProgram.cpp:2283:9 frame #7: 0x0000000100c04b4b MyProgram`QtPrivate::FunctorCall<QtPrivate::IndexesList<>, QtPrivate::List<>, void, void (MyObject::*)()>::call(f=50 ff be 00 01 00 00 00 00 00 00 00 00 00 00 00, o=0x00007f9f9a005950, arg=0x000000030597b390)(), MyObject*, void**) at qobjectdefs_impl.h:152:13 frame #8: 0x0000000100c04a8d MyProgram`void QtPrivate::FunctionPointer<void (MyObject::*)()>::call<QtPrivate::List<>, void>(f=50 ff be 00 01 00 00 00 00 00 00 00 00 00 00 00, o=0x00007f9f9a005950, arg=0x000000030597b390)(), MyObject*, void**) at qobjectdefs_impl.h:185:13 frame #9: 0x0000000100c04982 MyProgram`QtPrivate::QSlotObject<void (MyObject::*)(), QtPrivate::List<>, void>::impl(which=1, this_=0x00006000034ff200, r=0x00007f9f9a005950, a=0x000000030597b390, ret=0x0000000000000000) at qobjectdefs_impl.h:418:17 frame #10: 0x000000010c9afe85 QtCore`void doActivate<false>(QObject*, int, void**) [inlined] QtPrivate::QSlotObjectBase::call(this=<unavailable>, r=<unavailable>, a=0x000000030597b390) at qobjectdefs_impl.h:398:51 [opt] frame #11: 0x000000010c9afe70 QtCore`void doActivate<false>(sender=0x00007f9f9a005a58, signal_index=3, argv=0x000000030597b390) at qobject.cpp:3923 [opt] frame #12: 0x000000010c9b94f2 QtCore`QTimer::timerEvent(QTimerEvent*) [inlined] QTimer::timeout(_t1=QPrivateSignal @ 0x000000030597b388) at moc_qtimer.cpp:205:5 [opt] frame #13: 0x000000010c9b94cd QtCore`QTimer::timerEvent(this=0x00007f9f9a005a58, e=<unavailable>) at qtimer.cpp:257 [opt] frame #14: 0x000000010c9a76ef QtCore`QObject::event(this=0x00007f9f9a005a58, e=0x000000030597b720) at qobject.cpp:1324:9 [opt] frame #15: 0x0000000109bf0afa QtWidgets`QApplicationPrivate::notify_helper(this=<unavailable>, receiver=0x00007f9f9a005a58, e=0x000000030597b720) at qapplication.cpp:3640:26 [opt] frame #16: 0x0000000109bf1f21 QtWidgets`QApplication::notify(this=<unavailable>, receiver=0x00007f9f9a005a58, e=0x000000030597b720) at qapplication.cpp:0:9 [opt] frame #17: 0x000000010c97c304 QtCore`QCoreApplication::notifyInternal2(receiver=0x00007f9f9a005a58, event=0x000000030597b720) at qcoreapplication.cpp:1064:18 [opt] frame #18: 0x000000010c9e1f5f QtCore`QTimerInfoList::activateTimers(this=<unavailable>) at qtimerinfo_unix.cpp:643:13 [opt] frame #19: 0x000000010acc6cd2 libqcocoa.dylib`QCocoaEventDispatcherPrivate::activateTimersSourceCallback(void*) [inlined] QCocoaEventDispatcherPrivate::processTimers(this=0x00007f9f9a10a850) at qcocoaeventdispatcher.mm:131:35 [opt] frame #20: 0x000000010acc6cc9 libqcocoa.dylib`QCocoaEventDispatcherPrivate::activateTimersSourceCallback(info=0x00007f9f9a10a850) at qcocoaeventdispatcher.mm:125 [opt]
As per the stack trace, it is crashing in the
process
method that gets called from thetimeoutFired
method. Another weird thing is that I am printing a log in the first line oftimeoutFired
but it doesn't get printed when the app crashes. The program is compiled with-O0
.Any idea what could be happening here? I am not sure if the backtrace makes sense, but I am able to reproduce the issue 100% of the time and it always has the same backtrace.
-
First please post the actual code which really runs - your code above does not compile at all. Then please minimize your code so we can run it also. Since it's crashing in process() - what does this function do?
When you call stop() then the timer stops - except you call it from another thread then you will get a runtime warning.
-
@Christian-Ehrlicher This is actually part of a larger program. Let me see if I can strip it, but I am not too sure if the problem is with the timer or I am getting an incorrect stack trace. The
process
method copiesProbes
that have timed out to aQList
. The problem is that even iftimeoutFired
gets called,m_pending
is empty at that point and all that code should have been skipped including the for-loop. If I add logs in thetimeoutFired
method then they don't get printed in the invocation where it crashes which makes me doubtful that it was called.Also, the program is single-threaded.
-
I experienced that the (last) pending event can be fired despite the timer is stopped !
-
@mpergand said in QTimer doesn't stop on calling stop():
I experienced that the (last) pending event can be fired despite the timer is stopped !
Only when the event is already in the event queue.
-
@Christian-Ehrlicher said in QTimer doesn't stop on calling stop():
Only when the event is already in the event queue.
Correct, could be problematic.
-
@Christian-Ehrlicher Is this possible with a direct connection? Even then it doesn't explain why the logs are not getting printed. I also tried setting a debugger breakpoint but it is also not working for the invocation where it crashes.
-
@schrute said in QTimer doesn't stop on calling stop():
Is this possible with a direct connection?
I don't see what this has to do with the connection. We were talking about the fact that you call stop() within a slot while in the Qt event queue the timer event is already added. Then the slot might get called but I'm unsure about it. You have to read the source code wrt this to be really sure. But I doubt this is your problem.
-
@schrute
As @Christian-Ehrlicher said,QTimer::timeout
can be posted before and delivered after the timer was stopped.
QTimer::stop()
removes the timer synchronously, which means no more signals are emitted from that moment.
Disconnecting a driver when no longer used, is never a bad idea. That could be implemented in a small functionstopTimer()
. However, it does not prevent previously emitted signals from being delivered.
Theactive
property should normally be synchronous, but I am not sure if it can leak a bit behind (it's a computed property and not a simple bool).In
void timeoutFired()
,QTimer::isActive()
is used as a proxy assertion thatm_pending
has content.
That's a like hazarding a bet that nobody is home, because the doorbell remains unanswered. The proxy assertion bet is lost, when the inhabitant was stuck on the loo ;-) It may be better to checkm_pending
directly:void timeoutFired() { if (m_pending.isEmpty()) { qDebug() << __FUNCTION__ << "without probes"; return; } qDebug() << __FUNCTION << "with" << m_pending.count() << "probes"; auto pivot = std::partition(m_pending.begin(), m_pending.end(), [this](const Probe& probe) { return !probe.expired; }); for (auto it = pivot; it != m_pending.end(); ++it) { process(*it); } m_pending.resize(std::distance(m_pending.begin(), pivot)); }
If I add logs in the timeoutFired method then they don't get printed in the invocation where it crashes which makes me doubtful that it was called.
The stack trace is clear about
timeoutFired
being the crime scene. So either the source with theqDebug()
is newer than the binary or the crash kicks in beforeqDebug()
has had time to speak up. -
@Axel-Spoerl The issue was with my build setup. After a clean build the debugger started behaving properly and I was able to resolve the crash.