How to prevent slot re-entrancy



  • Hello,

    So, I'm developing a system that allows an external computer (lets say a microcontroller) send commands to a desktop application via a serial interface. My idea is to have one object that receives the sent bytes and it builds up remote function call packets from the received bytes and then signals the slots of other objects that the remote function calls were meant for.

    However, this is my problem. Let's say I have a GUI object that has a bunch of remote function call slots. They are just normal slots except that they are not meant to be executed by the user using the graphical user interface. As long as each of the slots for this GUI object do not return to the event loop while executing then their is no re-entrancy into the object and everything is good. However, a few of the slots I have in the object can take a while to execute. In these functions, to prevent the GUI from looking up I use an event loop. However, this also means that that re-entrancy can occur if a slot for this object is called again.

    So, my problem is this... how do I make sure that only one slot in a object may be called at a time? What I really want is a blockSlots() function like the block signals function. But, qt doesn't have this.

    This problem is something I've run into multiple times now working with Qt and I don't know how to solve it. Maybe I'm structuring my code badly, and if so, how could I change my structure to fix this?

    I can't use a thread to solve this problem because this is a GUI object. I want to use a thread to run the object that dispatches the remote function calls from bytes received from the serial port.


  • Moderators

    You could create a QMutexLocker at the start of your slot -- that will force your other calls to wait until the current one has finished. (Note: unlike blockSignals(), this doesn't PREVENT the slot from running -- it justs queues up the call)



  • But that will block the GUI thread. I don't believe mutexs use event loops or have signals for when they are available.

    The best thing to do I think would be to just make sure the event loop is not re-entered.



  • Well, you don't have to run your data connexion code on the main (GUI) thread. You can create a worker thread that will do all the data transmission and then signal the main thread that the data (whatever it may be) is ready to be displayed. I you use a queued connexion between a signal in your worker thread and your GUI objects, the GUI slots will be called on the main thread. Would that work for you?

    Edit: I know you said it is a GUI object, but why is it that you cannot split it and have all the slots run in a different thread?



  • Well, that would work...

    But, here's the basic issue. I have a function that does some repainting of a widget. While it doing this repainting it also needs to compute an variable number of FFTs.

    So... I use the QtConcurrent::map() function to do that. And then I have to wait on the map function with the GUI thread. To keep the GUI responsive I have an event loop hooked up to a QFutureWatcher.

    This is good, except for that that while I'm waiting for the map() function to return since I entered the event loop the GUI thread may re-enter the code again before it was done previously.

    And bad things will happen then.

    I think Qt should have a better mechanism for preventing slot re-entrancy. This is mentioned in guides I have read and all I see about it is the flag to exclude user events. But, that only works for user events and not all events.

    The only acceptable way I can seen to do everything is to just not have any slots re-enter the event loop.



  • I wouldn't be so quick to dismiss QMutex. You can use tryLock so that it's not blocking and in the case of a failure to acquire the lock, just save whatever is needed for deferred handling of the event and immediately return. Still, it's almost certainly easier to rethink the design that has you performing expensive computations while repainting.


  • Moderators

    You're right; I overlooked the blocking of the GUI thread. However, I don't think we should promote designing classes that mix explicitly reentrant components with explicitly non-reentrant components; the two components should be in separate classes for robustness.

    How often does the repainting occur? If it's very frequent, you could have another thread calculate the FFTs on the fly, and have the repainter grab the latest available results. If it's sporadic, you could do 2-step painting:

    Repaint all the "fast" bits first, and display old FFT data

    Signal your worker thread to compute new FFTs

    When done, signal the GUI to repaint again with new FFTs

    Have a look at the "spectrum analyzer example":http://blog.qt.digia.com/blog/2010/05/18/qtmultimedia-in-action-a-spectrum-analyser/ . It has a Spectrograph widget displaying audio spectrum that is computed in a worker thread, while maintaining a responsive GUI.



  • [quote author="Kwabena" date="1371831128"]However, this is my problem. Let's say I have a GUI object that has a bunch of remote function call slots. They are just normal slots except that they are not meant to be executed by the user using the graphical user interface. As long as each of the slots for this GUI object do not return to the event loop while executing then their is no re-entrancy into the object and everything is good. However, a few of the slots I have in the object can take a while to execute. In these functions, to prevent the GUI from looking up I use an event loop. However, this also means that that re-entrancy can occur if a slot for this object is called again.

    So, my problem is this... how do I make sure that only one slot in a object may be called at a time? What I really want is a blockSlots() function like the block signals function. But, qt doesn't have this.[/quote]

    I think the proper way is to re-think your code design. Creating an event-loop inside a slot function may not be a good idea to begin with! Why do the lengthy calculations in a slot function? In the main thread? Better move that into a "background" thread and just let the thread send a queued signal to the GUI as soon as the calculations are done.

    Anyway, I think implementing the blockSlots() is as simple as:
    @void MyClass::someSlot(...)
    {
    if(m_blocked) return;
    m_blocked = true;

    /* do other stuff here */
    
    m_blocked = false;
    

    }@

    You would need that in all slots, of course. But you don't need a Mutex here, I think. That's because the slots of the GUI class really should only be called (directly) from the "main" thread. Even if you use "background" worker threads, these will normally use queued connections, so the signals will be processed by the event-loop of the "main" thread, even if they were sent from another thread... Note, however, that "blocking" signals this way can cause signals to get lost completely! Your app needs to be prepared for that...



  • Thank you everyone for the suggestions. I've just gone the lazy route for now and I have the main thread blocking while an FFT happens. I'm using FFTW to get things done with multi-threading support enable so I think stuff will be fast enough. I have a limiter in the code that limits things to 50 Hz. I can lower the frame rate if necessary to because 50 Hz is overkill.

    The problem with what I am doing is that for the widget I am talking about... it is remotely controlled via external serial function calls. Part of the remote control functionality is the ability to delete the graphs that FFT's are being computed for. Because it is remote controlled, I have to execute slots in the order that they were called and I can't ignore slot execution. This coupled with the state of the object being able to change massively while a re-draw happens makes things very tricky.

    So... the only way to go would be to have the main thread copy all the data it has about graphs I am displaying, pass that to worker threads, and then copy the results back. This would then allow me to split the repaint process into two steps.

    Anyway, lazy route for now until blocking becomes a problem. I've spent to much time on this. :)


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.