PDA

View Full Version : Preventing a Slot to be called again before it has finished running



MattK
16th April 2012, 03:31
Hi,

Normally when a Widget's signal (f.e QSlider's valueChanged(int) signal) is connected to a given Slot, then this slot is not called again by the same signal as long as it has not returned.

This means that if you are moving the QSlider faster than its corresponding slot can run, your QSlider will simply "lag" when you move it becaise it will wait for the slot to follow. You can't move it faster than the Slot can run. This will allows the QSlider and its Slot to stay well synch.

Now, I have noticed that if a slot contains a call to some external DLL function, then this is not true anymore: multiple re-entrant calls can be done on this slot by the same widget's signal! This is extremely annoying in case of a QSlider moving faster than its slot can run because the Slot is re-called before it has finished running, thus making some sort of recursive or re entrant calls. It's like if the dll call inside the Slot was "broken" as it runs by the event loop or something. What is causing this curious behavior and how to prevent it? (of course I can always block or break the Widget's signal-slot connection inside its own slot and reactivate it as the slot is finished, or even disable the widget as the slot is running, but maybe there is a much nicer/cleaner way?)

--
mk

amleto
16th April 2012, 10:27
I think there is something else going on there...

No matter whether connections are synchronous or asynch, a signal cannot be fired again until the previous signal has returned.

I think a screen shot of a call stack would be handy.

wysota
16th April 2012, 12:07
Are you using threads in your application? There is simply no way a single slot can be called concurrently if things were done properly (that is if you're not using direct connections across threads).

MattK
17th April 2012, 07:58
...
I think a screen shot of a call stack would be handy.

Hi amleto, thanks for the suggestion. I did it.

So below is the code that gives the evidence (or at least the impression) that the slot was re-entered, using a re-entrance counter variable:





// Well, yes I know I should give the complete code, but it's really huge :(

SelectSource::SelectSource(QWidget *parent)
: QWidget(parent)
{
n = 0; // slot re-entrancy counter
...
}

void SelectSource::createToolbar()
{
...
connect(qSliderFocus, SIGNAL(valueChanged(int)), this, SLOT(updateFocusLabel())); // there is no other signal connected to this slot nor any direct call to this slot.
...
}

void SelectSource::updateFocusLabel()
{
if (n)
if (globalConsole)
globalConsole->sendMsg(QObject::tr("n==%1, meaning this slot was re-entered before it finished").arg(n)); /* Normally impossible to reach. Break Point line was set here */

if (globalConsole)
globalConsole->sendMsg(QObject::tr("Start focus loop..."));

n++; // should be == 1, never more


// This calls a DLL function. Content of DLL function closed and unknown to me.
// Note that this dll function has a timeout argument in its parameter.
// This means there is maybe some multithread logic hidden behind. Still, this does not explain why the slot updateFocusLabel() is re-entered.

focusCam();

n--; // must get back to 0.

if (globalConsole)
globalConsole->sendMsg(QObject::tr("Stop focus loop."));

}



And then the stack as given by netbeans using MinGW (windows is large, please scroll horizontally) + breakpoint at line 21 on the code above. I do not see a clear re-entrancy in the slot through this stack, but still, if I reached this breakpoint it means that n>0 when starting the slot() so that the slot was re-entered before it finished.
Yes, app is single threaded.



org.netbeans.modules.viewmodel.TreeModelNode@e3eed 5[Name=, displayName=SelectSource::updateFocusLabel(this=0x c8730e8) at selectsource.cpp:202] C:\\Users\\Matt-usr\\Documents\\CTA\\ProjectFiles\\QtIRView\select source.cpp:202
org.netbeans.modules.viewmodel.TreeModelNode@550a6 9[Name=, displayName=SelectSource::qt_metacall(this=0xc8730 e8,_c=QMetaObject::InvokeMetaMethod,_id=11,_a=0x22 a408) at moc.tmp/moc_selectsource.cpp:107] C:\\Users\\Matt-usr\\Documents\\CTA\\ProjectFiles\\QtIRView\moc.tm p\moc_selectsource.cpp:107
org.netbeans.modules.viewmodel.TreeModelNode@11e9f c6[Name=, displayName=QMetaObject::metacall(object=0xc8730e8 ,cl=QMetaObject::InvokeMetaMethod,idx=38,argv=0x22 a408) at kernel\\qmetaobject.cpp:237] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qmetaobject.cpp:237
org.netbeans.modules.viewmodel.TreeModelNode@18bb6 14[Name=, displayName=QMetaObject::activate(sender=0xc8c45d8 ,m=0x13dedc0,local_signal_index=0,argv=0x22a408) at kernel\\qobject.cpp:3278] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qobject.cpp:3278
org.netbeans.modules.viewmodel.TreeModelNode@15f08 f9[Name=, displayName=QAbstractSlider::valueChanged(this=0xc 8c45d8,_t1=888) at tmp\\moc\\debug_shared\\moc_qabstractslider.cpp:18 2] C:\Users\Matt-usr\Documents\CTA\bin\tmp\\moc\\debug_shared\\moc_ qabstractslider.cpp:182
org.netbeans.modules.viewmodel.TreeModelNode@46466 6[Name=, displayName=QAbstractSlider::setValue(this=0xc8c45 d8,value=888) at widgets\\qabstractslider.cpp:543] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qabstractslider.cpp :543
org.netbeans.modules.viewmodel.TreeModelNode@1b80e 2d[Name=, displayName=QAbstractSlider::triggerAction(this=0x c8c45d8,action=QAbstractSlider::SliderMove) at widgets\\qabstractslider.cpp:632] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qabstractslider.cpp :632
org.netbeans.modules.viewmodel.TreeModelNode@aac5d b[Name=, displayName=QAbstractSlider::setSliderPosition(thi s=0xc8c45d8,position=888) at widgets\\qabstractslider.cpp:500] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qabstractslider.cpp :500
org.netbeans.modules.viewmodel.TreeModelNode@1a3c2 bf[Name=, displayName=QSlider::mouseMoveEvent(this=0xc8c45d8 ,ev=0x22abfc) at widgets\\qslider.cpp:514] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qslider.cpp:514
org.netbeans.modules.viewmodel.TreeModelNode@46a30 e[Name=, displayName=QWidget::event(this=0xc8c45d8,event=0x 22abfc) at kernel\\qwidget.cpp:8244] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qwidget.cpp:8244
org.netbeans.modules.viewmodel.TreeModelNode@17b28 cc[Name=, displayName=QAbstractSlider::event(this=0xc8c45d8, e=0x22abfc) at widgets\\qabstractslider.cpp:942] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qabstractslider.cpp :942
org.netbeans.modules.viewmodel.TreeModelNode@b3751 f[Name=, displayName=QSlider::event(this=0xc8c45d8,event=0x 22abfc) at widgets\\qslider.cpp:435] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qslider.cpp:435
org.netbeans.modules.viewmodel.TreeModelNode@1ce51 dd[Name=, displayName=QApplicationPrivate::notify_helper(thi s=0x3f3a78,receiver=0xc8c45d8,e=0x22abfc) at kernel\\qapplication.cpp:4462] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication.cpp:446 2
org.netbeans.modules.viewmodel.TreeModelNode@60b54 e[Name=, displayName=QApplication::notify(this=0x22ff20,rec eiver=0xc8c45d8,e=0x22abfc) at kernel\\qapplication.cpp:4023] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication.cpp:402 3
org.netbeans.modules.viewmodel.TreeModelNode@11f46 57[Name=, displayName=QCoreApplication::notifyInternal(this= 0x22ff20,receiver=0xc8c45d8,event=0x22abfc) at kernel\\qcoreapplication.cpp:731] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qcoreapplication.cpp :731
org.netbeans.modules.viewmodel.TreeModelNode@43181 4[Name=, displayName=QCoreApplication::sendSpontaneousEvent (receiver=0xc8c45d8,event=0x22abfc) at ../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:218] C:\Users\Matt-usr\Documents\CTA\bin\..\..\include\QtCore\..\..\s rc\corelib\kernel\qcoreapplication.h:218
org.netbeans.modules.viewmodel.TreeModelNode@e1ca3 5[Name=, displayName=QApplicationPrivate::sendMouseEvent(re ceiver=0xc8c45d8,event=0x22abfc,alienWidget=0xc8c4 5d8,nativeWidget=0x22feb0,buttonDown=0x15db41c,las tMouseReceiver=@0x15db420: {o = 0xc8c45d8},spontaneous=true)
at kernel\\qapplication.cpp:3120] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication.cpp:312 0
org.netbeans.modules.viewmodel.TreeModelNode@1a25f 0d[Name=, displayName=QETWidget::translateMouseEvent(this=0x 22feb0,msg=@0x22b0f8: {hwnd = 0x150374, message = 512, wParam = 1, lParam = 2163086, time = 2273640, pt = {x = 398, y = 55}})
at kernel\\qapplication_win.cpp:3321] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication_win.cpp :3321
org.netbeans.modules.viewmodel.TreeModelNode@10e69 fd[Name=, displayName=QtWndProc@16(hwnd=0x150374,message=512 ,wParam=1,lParam=2163086) at kernel\\qapplication_win.cpp:1659] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication_win.cpp :1659
org.netbeans.modules.viewmodel.TreeModelNode@1507d 48[Name=, displayName=USER32!IsWindowVisible()] C:\\Windows\\system32\\user32.dll
org.netbeans.modules.viewmodel.TreeModelNode@1510b e0[Name=, displayName=Address: [@0x00150374]]
org.netbeans.modules.viewmodel.TreeModelNode@1e2a3 e7[Name=, displayName=Address: [@0x00000200]]
org.netbeans.modules.viewmodel.TreeModelNode@76a72 6[Name=, displayName=USER32!IsWindowVisible()] C:\\Windows\\system32\\user32.dll
org.netbeans.modules.viewmodel.TreeModelNode@1d7b8 3b[Name=, displayName=qt_is_translatable_mouse_event(message =0) at kernel\\qapplication_win.cpp:1426] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication_win.cpp :1426
org.netbeans.modules.viewmodel.TreeModelNode@18059 9e[Name=, displayName=USER32!IsWindowVisible()] C:\\Windows\\system32\\user32.dll
org.netbeans.modules.viewmodel.TreeModelNode@17bd4 70[Name=, displayName=Address: [@0x00000000]]


Well, it's like if the DLL call was fooling the signal-slot mechanism and would make the QSlider believe the slot is finished when it's not.


Are you using threads in your application? There is simply no way a single slot can be called concurrently if things were done properly (that is if you're not using direct connections across threads).

Hi wysota,

I said before I was not using threads. Well, I use threads but not for this signal->slot connection. The thread I use is communicating through normal connections, that are normally queued by default when Qt detects it's a crossthread connect.

Here is the only threaded code I use, unrelated to this issue i think:




SelectSource::SelectSource(QWidget *parent)
: QWidget(parent)
{
...

QThread *thread = new QThread;
getRingElement = new GetRingElement(); // must not have a parent as this would prevent moveToThread() working

connect(getRingElement, SIGNAL(msg4console(QString)), globalConsole, SLOT(sendMsg(QString)));
connect(this, SIGNAL(updateACQThreadRE(bool, int, int, int)), getRingElement, SLOT(setRE(bool, int, int, int)));
connect(this, SIGNAL(startACQThreadTimer(int)), getRingElement, SLOT(start(int)));
connect(this, SIGNAL(stopACQThreadTimer()), getRingElement, SLOT(stop()));
getRingElement->moveToThread(thread); // hmm well maybe I should connect only after the code is moved to a separated thread to ensure the queued mode is activated. I could also simply give the queued mode explicitly.
thread->start();
...
}


Coming back to the re-entered slot, here is the DLL function called:




in a .h:

extern int (_stdcall * vc_operate_command)(int Handle,char * Command,char * Answer,const int TimeOut);

in a .cpp:

int (_stdcall * vc_operate_command)(int Handle,char * Command,char * Answer,const int TimeOut)=NULL;

in another .cpp:

vc_operate_command = (int (_stdcall *)(int, char *, char *, const int))GetProcAddress(hInstance,"vc_operate_command");

Now when used, the command is called as:

result = vc_operate_command(Vcam->Handle, (char*)(command.toAscii()).constData(), answer, TIMEOUT_ANS);



I do not link the DLL implicitly (i.e with a *.lib) as the DLL was created by a third party who used the Microsoft compiler whereas I use MinGW. To be noticed that this command is acting on some hardware (advanced camera motorized focus) and thus, must have some pause, delay etc... function to let the HW the time to move.

wysota
17th April 2012, 11:53
The backtrace you provided doesn't say anywhere the slot was re-entered. I trust the stack more than your "n" variable :) What does focusCam() do?

MattK
17th April 2012, 17:07
The backtrace you provided doesn't say anywhere the slot was re-entered. I trust the stack more than your "n" variable :) What does focusCam() do?

I totally agree. I don't see any re-entrancy neither.

The focusCam() focuses a camera. I gave some details about the function used in focusCam () in the previous post just below the:
Coming back to the re-entered slot, here is the DLL function called:

I managed to get a log of:

bracktrace + thread status + content of most var of the class,

when n == 0 and then once again when n == 1, from the same execution.

Both flows in the backtrace are identical, just a very few arguments have changed. This is *extremely* weird I have rarely seen such a thing before. So just n is changing. I'm going to diff the 2 logs and post the log + the diffs here to really see what the differences are between the 2. This could give more clues.

ps: just discovered wwWidgets, pretty nice! I have to see where I can use those :-)

wysota
17th April 2012, 22:18
I suggest to output two messages to the debugging channel -- one when you enter the function and another one when you return from it. You'll then be able to tell whether you have two "entering" messages in a row without a "leaving" message inbetween.

MattK
18th April 2012, 02:20
I suggest to output two messages to the debugging channel -- one when you enter the function and another one when you return from it. You'll then be able to tell whether you have two "entering" messages in a row without a "leaving" message inbetween.

Good idea.

So Slot's Code is (I give it in case I did a mistake, we never know):



void SelectSource::updateFocusLabel()
{
qDebug() << "Entering: " << __PRETTY_FUNCTION__;

n++;
focusCam();
n--;

qDebug() << "Quiting " << __PRETTY_FUNCTION__;
}



Then moving the qSlider with the mouse gives that (5 successive slot calls in this case, one re-entered):



Entering: void SelectSource::updateFocusLabel()
Entering: void SelectSource::updateFocusLabel()
Quiting void SelectSource::updateFocusLabel()
Entering: void SelectSource::updateFocusLabel()
Quiting void SelectSource::updateFocusLabel()
Entering: void SelectSource::updateFocusLabel()
Quiting void SelectSource::updateFocusLabel()
Entering: void SelectSource::updateFocusLabel()
Quiting void SelectSource::updateFocusLabel()
Quiting void SelectSource::updateFocusLabel()


The re-entrance only "happens" (if it realy happens) once, possibly the DLL tries to run its function on the first time which takes say 0.5 second (that's a lot). Then, the DLL detects the same function is called again but since it has not returned from the previous call yet it returns from the next 4 successive calls immediately. This may explain why we do not "re-enter" more than once.

Now backlog of callstack + threads id + class content and address for the first call (n==0 when entering the slot). The underligned sections represent what changed in the log when we re-entered the slot (s when n==1). Nothing special!

n == 0, "1st entrance"

[Name=, displayName=SelectSource::updateFocusLabel(this=0x ea12eb8) at selectsource.cpp:200] C:\\Users\\Matt-usr\\Documents\\CTA\\ProjectFiles\\QtIRView\select source.cpp:200
[Name=, displayName=SelectSource::qt_metacall(this=0xea12e b8,_c=QMetaObject::InvokeMetaMethod,_id=11,_a=0x22 cf58) at moc.tmp/moc_selectsource.cpp:107] C:\\Users\\Matt-usr\\Documents\\CTA\\ProjectFiles\\QtIRView\moc.tm p\moc_selectsource.cpp:107
[Name=, displayName=QMetaObject::metacall(object=0xea12eb8 ,cl=QMetaObject::InvokeMetaMethod,idx=38,argv=0x22 cf58) at kernel\\qmetaobject.cpp:237] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qmetaobject.cpp:237
[Name=, displayName=QMetaObject::activate(sender=0xea64550 ,m=0x380edc0,local_signal_index=0,argv=0x22cf58) at kernel\\qobject.cpp:3278] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qobject.cpp:3278
[Name=, displayName=QAbstractSlider::valueChanged(this=0xe a64550,_t1=778) at tmp\\moc\\debug_shared\\moc_qabstractslider.cpp:18 2] C:\Users\Matt-usr\Documents\CTA\bin\tmp\\moc\\debug_shared\\moc_ qabstractslider.cpp:182
[Name=, displayName=QAbstractSlider::setValue(this=0xea645 50,value=778) at widgets\\qabstractslider.cpp:543] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qabstractslider.cpp :543
[Name=, displayName=QAbstractSlider::triggerAction(this=0x ea64550,action=QAbstractSlider::SliderMove) at widgets\\qabstractslider.cpp:632] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qabstractslider.cpp :632
[Name=, displayName=QAbstractSlider::setSliderPosition(thi s=0xea64550,position=778) at widgets\\qabstractslider.cpp:500] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qabstractslider.cpp :500
[Name=, displayName=QSlider::mouseMoveEvent(this=0xea64550 ,ev=0x22d74c) at widgets\\qslider.cpp:514] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qslider.cpp:514
[Name=, displayName=QWidget::event(this=0xea64550,event=0x 22d74c) at kernel\\qwidget.cpp:8244] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qwidget.cpp:8244
[Name=, displayName=QAbstractSlider::event(this=0xea64550, e=0x22d74c) at widgets\\qabstractslider.cpp:942] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qabstractslider.cpp :942
[Name=, displayName=QSlider::event(this=0xea64550,event=0x 22d74c) at widgets\\qslider.cpp:435] C:\Users\Matt-usr\Documents\CTA\bin\widgets\\qslider.cpp:435
[Name=, displayName=QApplicationPrivate::notify_helper(thi s=0xddb3a78,receiver=0xea64550,e=0x22d74c) at kernel\\qapplication.cpp:4462] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication.cpp:446 2
[Name=, displayName=QApplication::notify(this=0x22ff20,rec eiver=0xea64550,e=0x22d74c) at kernel\\qapplication.cpp:4023] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication.cpp:402 3
[Name=, displayName=QCoreApplication::notifyInternal(this= 0x22ff20,receiver=0xea64550,event=0x22d74c) at kernel\\qcoreapplication.cpp:731] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qcoreapplication.cpp :731
[Name=, displayName=QCoreApplication::sendSpontaneousEvent (receiver=0xea64550,event=0x22d74c) at ../../include/QtCore/../../src/corelib/kernel/qcoreapplication.h:218] C:\Users\Matt-usr\Documents\CTA\bin\..\..\include\QtCore\..\..\s rc\corelib\kernel\qcoreapplication.h:218
[Name=, displayName=QApplicationPrivate::sendMouseEvent(re ceiver=0xea64550,event=0x22d74c,alienWidget=0xea64 550,nativeWidget=0x22feb0,buttonDown=0x3a0b41c,las tMouseReceiver=@0x3a0b420: {o = 0xea64550},spontaneous=true) at kernel\\qapplication.cpp:3120] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication.cpp:312 0
[Name=, displayName=QETWidget::translateMouseEvent(this=0x 22feb0,msg=@0x22dc48: {hwnd = 0x103f8, message = 512, wParam = 1, lParam = 2359687, time = 2284728, pt = {x = 391, y = 58}}) at kernel\\qapplication_win.cpp:3321] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication_win.cpp :3321
[Name=, displayName=QtWndProc@16(hwnd=0x103f8,message=512, wParam=1,lParam=2359687) at kernel\\qapplication_win.cpp:1659] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication_win.cpp :1659
[Name=, displayName=USER32!IsWindowVisible()] C:\\Windows\\system32\\user32.dll
[Name=, displayName=Address: [@0x000103f8]]
[Name=, displayName=Address: [@0x00000200]]
[Name=, displayName=USER32!IsWindowVisible()] C:\\Windows\\system32\\user32.dll
[Name=, displayName=qt_is_translatable_mouse_event(message =0) at kernel\\qapplication_win.cpp:1426] C:\Users\Matt-usr\Documents\CTA\bin\kernel\\qapplication_win.cpp :1426
[Name=, displayName=USER32!IsWindowVisible()] C:\\Windows\\system32\\user32.dll
[Name=, displayName=Address: [@0x00000000]]

[Name=, displayName=12 thread 5884.0x17b4 0x77697094 in ntdll!LdrFindResourceEx_U ()]
[Name=, displayName=11 thread 5884.0x17ec 0x77697094 in ntdll!LdrFindResourceEx_U ()]
[Name=, displayName=10 thread 5884.0x17d8 0x77697094 in ntdll!LdrFindResourceEx_U ()]
[Name=, displayName=6 thread 5884.0x17a4 0x77697094 in ntdll!LdrFindResourceEx_U ()]
[Name=, displayName=5 thread 5884.0x1780 0x77697094 in ntdll!LdrFindResourceEx_U ()]
[Name=, displayName=4 thread 5884.0x174c 0x77697094 in ntdll!LdrFindResourceEx_U ()]
[Name=, displayName=3 thread 5884.0x177c 0x77697094 in ntdll!LdrFindResourceEx_U ()]
[Name=, displayName=2 thread 5884.0x1778 0x77697094 in ntdll!LdrFindResourceEx_U ()]
[Name=, displayName=1 thread 5884.0x16f8 SelectSource::updateFocusLabel (this=0xea12eb8) at selectsource.cpp:200]

[Name=, displayName=this] class SelectSource * const 0xea12eb8
[Name=, displayName=<Base class>] QWidget {<QObject> = {_vptr.QObject = 0x469688, static staticMetaObject = {d = {superdata = 0x0, stringdata = 0xcfb0a0 "QObject", data = 0xcfafe0, extradata = 0xcfb12c}}, d_ptr = {d = 0xea12f30}, static staticQtMetaObject = {d = {superdata = 0x0, stringdata = 0xd0c700 "Qt", data = 0xd0a160, extradata = 0x0}}}, <QPaintDevice> = {_vptr.QPaintDevice = 0x469770, painters = 0}, static staticMetaObject = {d = {superdata = 0xcfae08, stringdata = 0x3789120 "QWidget", data = 0x3788c60, extradata = 0x0}}, data = 0xea12fe8}
[Name=, displayName=ResEnhanAct] class QAction * 0xea55018
[Name=, displayName=autoFocusAct] class QAction * 0xea5b9b8
[Name=, displayName=connectCamAct] class QAction * 0xea15018
[Name=, displayName=filesAreTheSource] class QAction * 0xbaadf00d
[Name=, displayName=getRingElement] class GetRingElement * 0xea66a48
[Name=, displayName=n] int 0
[Name=, displayName=nucAct] class QAction * 0xea568b8
[Name=, displayName=pauseAct] class QAction * 0xea14e00
[Name=, displayName=qLabelFocus] class QLabel * 0xea641c0
[Name=, displayName=qLineEditCommand] class QLineEdit * 0xea601d8
[Name=, displayName=qSliderFocus] class QSlider * 0xea64550
[Name=, displayName=reDialog] class REDialog * 0xea56228
[Name=, displayName=reinitAct] class QAction * 0xea5b470
[Name=, displayName=sendCommandToCam] SendCommandToCam {<No data fields>}
[Name=, displayName=sourceMenu] class QMenu * 0xea0b930
[Name=, displayName=sourceToolBar] class QToolBar * 0xea5bac8
[Name=, displayName=spawnNewViewAct] class QAction * 0xea13200
[Name=, displayName=staticMetaObject] {d = {superdata = 0x37766ac, stringdata = 0x42f1c0 "SelectSource", data = 0x42f040, extradata = 0x0}}
[Name=, displayName=vc] VarioCam * 0xea13118

wysota
18th April 2012, 09:07
If you remove all the dll calls from your code, do you still get the same behaviour?

MattK
19th April 2012, 01:08
If you remove all the dll calls from your code, do you still get the same behaviour?

No, not at all. I forgot to mention that. Only the DLL seems to create this mess.

I though maybe the time needed to run the slot was an issue as the call to the dll may take up to 1 sec to complete, but it's not that. To be sure, if I remove the call to the dll, and replaced it by a for(int i=0, i<j; i++); with j large enough to create a 1 sec delay. In this case the slot runs exactly as expected: no re-entrency and the widget GUI lags because it can't move faster than the slot can run (which is good, so the GUi stay well synch with the code).

I'm gonna try some stuff with the queued mode of the connection. Maybe Qt is fooled with the DLL and thinks it is a separated thread, in which case it may force (implicitely?) a queued connect instead of a normal direct connect? Still, even a queued connect should not allow the slot to be re-entered.

ChrisW67
19th April 2012, 02:34
Unless the DLL does something that will trigger a signal that is connected to this slot I do not see how you get multiple calls. Does the DLL cause a resize of the window or change of data associated with qSliderFocus, triggering slider has to change value?

MattK
19th April 2012, 07:51
Unless the DLL does something that will trigger a signal that is connected to this slot I do not see how you get multiple calls. Does the DLL cause a resize of the window or change of data associated with qSliderFocus, triggering slider has to change value?

Thanks for the suggestion (I'll take anything since I ran out of idea). The DLL is part of a driver interfacing a scientific thermal infrared camera. It doesn't use/create any GUI. It's very basic. The DLL does not know my Qt application as the DLL was written by a 3rd party firm 5 years ago. The DLL as used in the Qt application takes a string that is a focus command. Then, the DLL gives a message back as a string. I read the string and throw this string into a custom Widget based console. This makes me think I must isolate the DLL call from anything else by not recovering the DLL's answer to see if the Slot still behave this way.

So:

1 - a slot can't be re-entered by the same widget's signal
2 - the call stack confirms the slot is not re-entered, in the case of 2 and 3 below.
3 - in practice the slot is (like) re-entered. A re-entrency counter (n) shows it.
4 - A qDebug message in the slot gave again the evidence the slot was (like) re-entered.

So we still have 2 very simple evidences (1,2) vs 2 opposite simple evidences (3,4). Interesting... what can cause such a thing?

Forgot to say this happens in both release and debug mode, so it's not a magical preemptive optimization trick that GCC did neither.

Lesiok
19th April 2012, 07:56
Maybe the DLL is calling QCoreApplication::processEvents.

amleto
19th April 2012, 10:29
print thread id next to your enter/quit statements.

also put try/catch around dll call (and print exception if there is one).

wysota
19th April 2012, 10:49
Does the DLL link to Qt? Does it use Qt? Does it use callbacks into your own code? If no then there is simply no way it can influence the way the slot is called, regardless if it uses threads or not.

MattK
20th April 2012, 02:03
Thx all for support again.

Answering to everybody in a single post with citation.



This makes me think I must isolate the DLL call from anything else by not recovering the DLL's answer to see if the Slot still behave this way.


I answer to myself here: isolating the DLL call in the slot, so the slot only calls the dll (the answer is not recoreved and not sent into the console widget) doesn't change anything.
I also tried to change the Qt::ConnectionType in the connect() to force it to queued or direct. This doesn't change anything.


Maybe the DLL is calling QCoreApplication::processEvents.

I wished it was this, but no. See post #12 for more about the DLL.


print thread id next to your enter/quit statements.

also put try/catch around dll call (and print exception if there is one).


In post #8 we see that only 1 thread is running. I have followed your idea anyway in case this helps to see something new we may have missed. We never know...

I'm publishing the code in case I did a mistake in the code:




void SelectSource::createToolbar()
{
...
connect(qSliderFocus, SIGNAL(valueChanged(int)), this, SLOT(updateFocusLabel()));
...
}

void SelectSource::updateFocusLabel()
{
qDebug() << "Entering: " << __PRETTY_FUNCTION__ << " Thread ID: " << QThread::currentThreadId();

n++;
char answer[5000];
vc_operate_command(vc->Vcam.Handle, ":focsteps 200,120", answer, 5000); // This is the DLL call that was buried in a local function "focusCam()" before.
n--;

qDebug() << "Quiting " << __PRETTY_FUNCTION__ << " Thread ID: " << QThread::currentThreadId();
}


Which gives:



Entering: void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Entering: void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Quiting void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Entering: void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Quiting void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Entering: void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Quiting void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Quiting void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Entering: void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Entering: void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Quiting void SelectSource::updateFocusLabel() Thread ID: 0x11a8
Quiting void SelectSource::updateFocusLabel() Thread ID: 0x11a8


Now, with a try/catch, I used this:



void SelectSource::updateFocusLabel()
{
qDebug() << "Entering: " << __PRETTY_FUNCTION__ << " Thread ID: " << QThread::currentThreadId();
n++;
try{
char answer[5000];
vc_operate_command(vc->Vcam.Handle, ":focsteps 200,120", answer, 5000); // #include "vc2dll.h"
}
catch (...) // we catch any potential exception of any type.
{
qDebug() << "Some exception happened";
}
n--;
qDebug() << "Quiting " << __PRETTY_FUNCTION__ << " Thread ID: " << QThread::currentThreadId();
}


This doesn't not give any exception (but is the try catch code even correct? I'm not used to use try/catch statements in my code)


Does the DLL link to Qt? Does it use Qt? Does it use callbacks into your own code? If no then there is simply no way it can influence the way the slot is called, regardless if it uses threads or not.

>the DLL link to Qt?
No
>Does it use Qt?
No
>Does it use callbacks into your own code?
No.
See msg #12 for more on DLL. The dll call is basically a black bo that does some stuffs (fcusing a camera). I call it as an ordinary and normal C function that has no link to Qt and no special link to my Qt code.

wysota
20th April 2012, 09:51
If you do get a double call then the only explanation I can see is that the DLL corrupts the stack somehow. I suggest you place a breakpoint on the dll function and step through execution of the code to see if it ends up calling the slot again.

d_stranz
22nd April 2012, 01:55
If you do get a double call then the only explanation I can see is that the DLL corrupts the stack somehow. I suggest you place a breakpoint on the dll function and step through execution of the code to see if it ends up calling the slot again.

After reading through, that is my conclusion as well. The "reentrancy" you are seeing is just an artifact of stack corruption.

Does the DLL expect arguments to the function you are calling (but maybe not providing)? Are your arguments of the same type expected by the DLL? If you are passing in a pointer to some buffer, is the buffer big enough? What happens if you change the size of buffers / arrays / whatever that are used by the DLL? Using global variables shared between the app and DLL? What happens if you rearrange them?

MattK
25th April 2012, 23:19
If you do get a double call then the only explanation I can see is that the DLL corrupts the stack somehow. I suggest you place a breakpoint on the dll function and step through execution of the code to see if it ends up calling the slot again.


After reading through, that is my conclusion as well. The "reentrancy" you are seeing is just an artifact of stack corruption.

Does the DLL expect arguments to the function you are calling (but maybe not providing)? Are your arguments of the same type expected by the DLL? If you are passing in a pointer to some buffer, is the buffer big enough? What happens if you change the size of buffers / arrays / whatever that are used by the DLL? Using global variables shared between the app and DLL? What happens if you rearrange them?

@both: The Slot is really called twice as shown by the qDebug or re-entrancy counter, but we do not see that on the stack. So I agree your theory of a corrupted stack makes a lot of sense. What's interesting is that it doesn't crash, and it really works as it should if re-entrancy was possible or as if this behavior was done on purpose.

@wysota: I can't debug while it is stacking because for this bug to happen the slot must be called twice fastly. The 2nd call must happens as the DLL call hasn't returned. But I can easily place a break point as the 2nd call is unstacked (returned) and see when the slot is re-entered to be unstacked (finished) from the 1st call.

@d_stranz: Thanks for suggestions. I'll try all that and publish the results here.

In the meantime I wrote a Slot that can handle re-entrancy with a queue. It works like a charm as expected. The Slot basically queues the arguments it receives and then reorders them in the right order: because of the recursive nature of re-entrency, slot's arguments are stacked as a FILO, so the last arguments received by the slot from the last are used first. But I want them to be used last instead like the slot were called "normally" without re-entrancy. So the queue in the Slot is read as a FIFO. Here is the code (seems very simple to follow, but it's not as it's a hack that counters an abnormal behavior):




struct QUEUE
{
// Queue for focus action
int idx;
int val[2];
};

QUEUE queue; // in header

queue.idx = 0; // in class constructor

void SelectSource::focusCam() // Slot connected to the qSlider:
{
// This queue can have 2 items (camera commands) at most.
// If adding new items as the queue is full, the last item is replaced with the new one.
// This is because we can't replace the first item as it was sent to sendCommandToCam.msg()
// already and because the 2nd item has got obsolete because of the new one.

// This function also convert the FILO behavior resulting from recursive slot re-entrancy
// calls into a normal FIFO so that future events do not happen first anymore.

// It can be difficult to follow this code because of the very unusual flow of Qt.
// The best is to start an example with 1 item added, then add another
// item while in call of sendCommandToCam.msg(&vc->Vcam, cmd); (assume this one takes a
// long time to return and that this slot keeps being called in the meantime with new values)

queue.val[(queue.idx==2)?1:queue.idx] = qSliderFocus->value();

if(queue.idx==2)
return;

queue.idx++;

if(queue.idx==2)
return;

do
{ // this loop can be only reached the first time this slot is called.
QString cmd;
cmd = QString(":focsteps %1,120").arg(queue.val[0],0,10);
sendCommandToCam.msg(&vc->Vcam, cmd); // Slot can be re-entered as this function runs for an unknown reason. Contains a DLL call.
queue.val[0] = queue.val[1];
} while (--queue.idx);
}

ChrisW67
26th April 2012, 02:39
Random thought: could this be the result of calling the DLL function using the wrong parameter passing arrangement, i.e. stdcall vs. cdecl ? If the called function clears the stack (and shouldn't) then the caller clears the stack again...
http://stackoverflow.com/questions/3404372/stdcall-and-cdecl

MattK
26th April 2012, 06:24
Random thought: could this be the result of calling the DLL function using the wrong parameter passing arrangement, i.e. stdcall vs. cdecl ? If the called function clears the stack (and shouldn't) then the caller clears the stack again...
http://stackoverflow.com/questions/3404372/stdcall-and-cdecl

This could explain the mess on the stack more than very well! In particular I had to link the dll explicitly. When you link a DLL implicitly i.e with a .lib, mixing the calling convention is not possible, but when it's linked explicitly, you have to pay attention to the calling convention. cdecl does not require the function to clear the stack, but requires the calling function to clear the stack. For stdcall the called fonction clears the stack instead not the calling function. Many user said you can mix the conventions, but I don't think it's true. Mixing them means the stack is either cleared twice (cdecl -> stdcall) or not at all (stdcall -> cdecl) which could in turn develop such a weird behavior without automatically crashing.. this is exactly what we seem to have. I'll change the calling conv. and see what it does!

d_stranz
26th April 2012, 18:16
I'll change the calling conv. and see what it does!

Please report your results. It would be interesting to know if this was the source of all the problems.

MattK
27th April 2012, 21:49
Random thought: could this be the result of calling the DLL function using the wrong parameter passing arrangement, i.e. stdcall vs. cdecl ? If the called function clears the stack (and shouldn't) then the caller clears the stack again...
http://stackoverflow.com/questions/3404372/stdcall-and-cdecl


Please report your results. It would be interesting to know if this was the source of all the problems.

Hi d_stranz, absolutely, I answer to all the suggestions, by respect for those suggesting the issues and for the community.

So, the code currently uses the _stdcall call convention (or __stdcall with two "_" depending on the compiler). I tried to import the dll function as _cdecl instead. It works but this doesn't do any difference, the slot is still recalled before it has "finished" exactly as before.

So

extern int (_stdcall * vc_operate_command)(int Handle,char * Command,char * Answer,const int TimeOut);

was changed to

extern int (_cdecl * vc_operate_command)(int Handle,char * Command,char * Answer,const int TimeOut);

btw: I have seen a few posts stating you can mix cdecl vs stdcall and vice versa, like the page you gave for reference for example. Although it's true it didn't crash when I changed _sdtcall for _cdecl on the caller side, I doubt very much you can mix them up. To support this, I have read a user mixing the 2 calling conventions by error there http://cboard.cprogramming.com/c-programming/109804-dynamic-link-dll-loadlibrary-corrupts-control-loop.html this may confirm this may not crash immediately but it ends up making a great mess, so it's not true cdecl and sdtcall can be mixed up. Anyway the idea here was not to mix them but only to change the calling convention of the caller in case it was not matching the one of the callee (dll) before.

I also tried the _fastcall convention, that is less common but we never know. This time the caller crashed immediately...

In conclusion, the calling convention issue is a very nice idea as a cause of this trouble, supporting the idea of a messed stack, but the calling convention is apparently not the reason of the "re-entered" slot.

PS: I say "re-entered" because it's not really re-entered or the call stack would show it, it's more subtle than that. I feel like the slot is forced to return as the dll runs it's function. So the event scheduler thinks the slot has finished running. This is perfectly possible, in assembly at least. At a much later time, the dll continue running where it stop and is like able to recover where it forced the slot return - This would mean the dll saved the whole context and stack - thus giving the impression of a re-entered slot. Just a speculation. I gotta check this now by carefully running it step by step as suggested by wysota.

Gotta test wysota and then d_stranz suggestions now.

Thanks!

d_stranz
27th April 2012, 22:05
extern int (_cdecl * vc_operate_command)(int Handle,char * Command,char * Answer,const int TimeOut);

This looks like a possible source of stack corruption. From the names, it would appear that "Answer" is a buffer into which the DLL is going to place some reply to the command. From the calling convention, it would appear that you must allocate this buffer yourself and pass is a pointer to it. What does the documentation say about how big this buffer must be? (Or does it say anything at all?) What do the contents look like after the call? Are you sure it is a char * and not a wchar *? Do you need to clear it and set the first character to NULL ('\0') before the call? What about "Command"? Does the DLL expect this to be a NULL-terminated string?

I'd start with the "Answer" :-) and go from there.

MattK
28th April 2012, 00:16
If you do get a double call then the only explanation I can see is that the DLL corrupts the stack somehow. I suggest you place a breakpoint on the dll function and step through execution of the code to see if it ends up calling the slot again.

Results are very interesting:



void SelectSource::updateFocusLabel() // this is a slot connected the qSlider
{
qDebug() << "enter";
if (idx)
{
; // for break point purposes...
}
idx++;
QString cmd;
cmd = QString(":focsteps %1,120").arg(qSliderFocus->value());
sendCommandToCam.msg(&vc->Vcam, cmd);
queue.idx--;
qDebug() << "exit";
}


QString SendCommandToCam::msg(VC *Vcam, QString command) const
{

int result;
char answer[MAXCHAR_ANS];
*answer = NULL; // clear the string

char *cmd = NULL;
QByteArray cmdArr;

cmdArr = command.toAscii();
cmd = (char*)cmdArr.constData();
result = vc_operate_command(Vcam->Handle, cmd, answer, TIMEOUT_ANS);
if (result != VC_RES_OK)
{ .... }
....
}


BR @ lines: 8,11,12 + 28,29,30

8; // now idx == 1
(continue)
11;
(continue)
28;
(continue)
29; // DLL call
(continue)
8; //now idx == 2. Note after 29, we did NOT reach 30.
(continue)
11;
(continue)
28;
(continue)
29;
(continue)
30; // now we reach 30, probably because 29 is already running and returns asap.
(continue)
12; // All good. Now idx == 1 again
(continue)
30; // note: after 12 we did NOT reach 28,29.
(continue)
12; // now idx is 0.

qDebug shows:

enter
enter
exit
exit

The trace show that the dll acts as a super return as it runs, and probably force a return for all the calls on the stack (it returns at least SendCommandToCam::msg() and then SelectSource::updateFocusLabel(), so I guess it forces a returns on the upper functions as well). This is why Qt thinks we have returned from the slot. In fact we have returned and this is also why we don't see any imbrication or recursive calls on the stack. So Qt is probably starting a new cycle on the event loop.

Whats interesting here is the way the DLL is able force a return (that could be a bug, for example if you clear the stack and readjust the program counter so it points to a base address from which the prog can restart smoothly). But then, the DLL forces a passage from line 12 to 30 without passing by 28, 29 like to continue where it stopped before.

A way to do that for the dll would be to run in another thread and conrol the stack of the working thread, and then clear the program counter (PC), save the Stack (eg save the stack pointer on the stack and restart a new stack from here) the first time, and then reverse everything at some point.

Added after 24 minutes:


This looks like a possible source of stack corruption. From the names, it would appear that "Answer" is a buffer into which the DLL is going to place some reply to the command. From the calling convention, it would appear that you must allocate this buffer yourself and pass is a pointer to it. What does the documentation say about how big this buffer must be? (Or does it say anything at all?) What do the contents look like after the call? Are you sure it is a char * and not a wchar *? Do you need to clear it and set the first character to NULL ('\0') before the call? What about "Command"? Does the DLL expect this to be a NULL-terminated string?

I'd start with the "Answer" :-) and go from there.

Like you and I wysota I tend to believe the dll is messing up the stack, either because of a bug or on purpose.
So yes as shown in the previous code I posted the char*answer must be allocated. The SDK says:



Command: The character string containing the command
Answer: Accepts the answer from camera. The length of the answer is to be defined corresponding to the expected maximum answer length.


"expected maximum answer length" How informative lol. So I allocated 5K by default, knowing by experience that the answer never seems longer than say 50 chars. To be sure, I allocated 500KBytes. Same behavior.

>What do the contents look like after the call?
I always display the content on a console after the call. It looks like a normal 20 chars string, null char terminated, that returns the command entered. It's a form of ack.

>Are you sure it is a char * and not a wchar *?
From the dll's SDK it's a char. From a dll analyzer, it's a char as well, so no wchar.

>Do you need to clear it and set the first character to NULL ('\0') before the call?
yes, always did as shown in code above.

>What about "Command"? Does the DLL expect this to be a NULL-terminated string?
The only info I have is the one cited above. As it's a very basic and low level dll, I assume it is a null terminated string as returned by QByteArray::constData(); To be noticed that ::constData() returns a const, hence the cast I did as "cmd = (char*)cmdArr.constData();".

raspou
7th November 2012, 17:23
Hello all,
I don't know if this thread is still active, but i'd like to give some random ideas regarding such strange behaviours:

- I often experienced kinda reentrance of slot calls if somewhere in my code is called QApplication::processEvents(), even if this code has no connection to the said slot. That sound logical because processEvents() should apply to the overall application event processing. The strange part is that calling processEvents() disrupts slot calls that way. Maybe you should check if such slot reentrancy does coincide with a processEvents() call.

- regarding the dll function call, maybe you could try passing your char * argument finished with two trailing \0 ("some text\0\0") as I remember having such stack corruption on some Microsoft function which was expecting string lists packed into char* (each string being terminated by an \0 plus an additional \0 at the end of it all).

- given the version of QT you link to, the event processing may behave differently (at least under windows). The whole event processing under windows has been refounded between Qt 4.5 and 4.6, and some annoying related issues had been fixed in Qt 4.8. It would be interesting to see if your symptom is different when linking against different Qt versions.

I would be happy to know if someone has a final word to this strange behaviour.

PS. forgive my English and my outrageous accent, I'm French. ;)