bgribble/mfp

toggle button/radiogroup combo works with wires, not vias

Closed this issue · 3 comments

In the attached image, the left version of the patch works but not the right. They should work identically.

radios

The failure mode is this: clicking on the button in the right patch (or any stripped-down version of it that includes a send/receive pair in the feedback loop) locks up MFP until the request times out 5 seconds later. The traceback on timeout looks like this:

[  42.503   main] [var]: calling gui_command.configure
[  42.503    gui] configure: handing off to clutter thread
[  47.508    gui] Exception while handling key command M1DOWN
[  47.510    gui] Traceback (most recent call last):
[  47.510    gui]   File "/home/grib/devel/mfp/wafbuild/virtual/lib/python2.7/dist-packages/mfp-0.05_git_5abdd9b-py2.7.egg/mfp/gui/input_manager.py", line 175, in handle_event
[  47.510    gui]     rv = self.handle_keysym(keysym)
[  47.511    gui]   File "/home/grib/devel/mfp/wafbuild/virtual/lib/python2.7/dist-packages/mfp-0.05_git_5abdd9b-py2.7.egg/mfp/gui/input_manager.py", line 105, in handle_keysym
[  47.511    gui]     handled = handler[0]()
[  47.512    gui]   File "/home/grib/devel/mfp/wafbuild/virtual/lib/python2.7/dist-packages/mfp-0.05_git_5abdd9b-py2.7.egg/mfp/gui/modes/clickable.py", line 40, in click
[  47.512    gui]     return self.widget.clicked()
[  47.512    gui]   File "/home/grib/devel/mfp/wafbuild/virtual/lib/python2.7/dist-packages/mfp-0.05_git_5abdd9b-py2.7.egg/mfp/gui/button_element.py", line 202, in clicked
[  47.513    gui]     MFPGUI().mfp.send(self.obj_id, 0, message)
[  47.514    gui]   File "/home/grib/devel/mfp/wafbuild/virtual/lib/python2.7/dist-packages/mfp-0.05_git_5abdd9b-py2.7.egg/mfp/rpc/rpc_wrapper.py", line 19, in inner
[  47.515    gui]     return self.call_remotely(rpcdata)
[  47.515    gui]   File "/home/grib/devel/mfp/wafbuild/virtual/lib/python2.7/dist-packages/mfp-0.05_git_5abdd9b-py2.7.egg/mfp/rpc/rpc_wrapper.py", line 104, in call_remotely
[  47.516    gui]     self.rpchost.wait(r, timeout=5)
[  47.517    gui]   File "/home/grib/devel/mfp/wafbuild/virtual/lib/python2.7/dist-packages/mfp-0.05_git_5abdd9b-py2.7.egg/mfp/rpc/rpc_host.py", line 157, in wait
[  47.517    gui]     raise Exception()
[  47.518    gui] Exception

The whole round-trip goes like this: Click, then send a bool to the underlying [var] is the "outgoing" leg, then the [var] calls configure with its new value and updates the GUI as the "return" leg. The last message before the traceback is the "return" leg of the round trip.

What we see in the log (locally-added log statements, you'll have to trust me) is that we have handed off the update thunk to the Clutter thread but it never gets called. Why? Because the Clutter thread is still waiting for the click handler to return.

OK, that sorta makes sense, but the click handler should have returned very shortly after the "handing off to clutter thread" message. So the next step is to figure out why it's not.

More debugging output. This is a trace of everything that happens in a simplified version of the above patch when the toggle goes from False to True.

[  26.394    gui] button: sending True to 1
[  26.394   main] send: 1 0 True
[  26.395   main] [var]: <mfp.builtins.var.Var object at 0x7ff961f3b750> triggered with [True, Uninit]
[  26.395   main] [var]: checking True {'value': False, 'position_x': 98.61260986328125, 'name': 'toggle_001', 'width': 20, 'on_message': True, 'no_export': False, 'update_required': True, 'height': 20, 'num_outlets': 1, 'dsp_inlets': [], 'off_message': False, 'display_type': 'toggle', 'num_inlets': 2, 'position_y': 172.55679321289062, 'is_export': False, 'dsp_outlets': [], 'layername': 'Layer 0', 'scope': '__patch__'}
[  26.395   main] [var]: calling gui_command.configure
[  26.396    gui] configure: handing off to clutter thread
[  26.396   main] [var]: returned from gui_command.configure
[  26.396   main] [var]: <mfp.builtins.var.Var object at 0x7ff961f3b750> trigger done
[  26.396   main] send: doing work item (<mfp.builtins.sendrcv.Send object at 0x7ff961f3b7d0>, True, 0) of 1
[  26.397   main] [send]: triggered <mfp.builtins.sendrcv.Send object at 0x7ff961f3b7d0>
[  26.397   main] [send]: in case 3 <mfp.builtins.sendrcv.MessageBus object at 0x7ff961f3b910>
[  26.397   main] send: doing work item (<mfp.builtins.sendrcv.Recv object at 0x7ff961f3b850>, True, 0) of 1
[  26.397   main] [recv]: [True, Uninit]
[  26.397   main] send: doing work item (<mfp.builtins.radiogroup.RadioGroup object at 0x7ff961f3b990>, True, 0) of 1
[  26.397   main] [radiogroup] <mfp.builtins.radiogroup.RadioGroup object at 0x7ff961f3b990>: triggered with [True, Uninit]
[  26.397   main] [radiogroup] <mfp.builtins.radiogroup.RadioGroup object at 0x7ff961f3b990>: trigger done
[  26.397   main] send: doing work item (<mfp.builtins.var.Var object at 0x7ff961f3b750>, True, 0) of 1
[  26.397   main] [var]: <mfp.builtins.var.Var object at 0x7ff961f3b750> triggered with [True, Uninit]
[  26.398   main] [var]: checking True {'value': True, 'position_x': 98.61260986328125, 'name': 'toggle_001', 'width': 20, 'on_message': True, 'no_export': False, 'update_required': True, 'height': 20, 'num_outlets': 1, 'dsp_inlets': [], 'off_message': False, 'display_type': 'toggle', 'num_inlets': 2, 'position_y': 172.55679321289062, 'is_export': False, 'dsp_outlets': [], 'layername': 'Layer 0', 'scope': '__patch__'}
[  26.398   main] [var]: <mfp.builtins.var.Var object at 0x7ff961f3b750> trigger done
[  26.398   main] send: doing work item (<mfp.builtins.sendrcv.Send object at 0x7ff961f3b7d0>, True, 0) of 1
[  31.402    gui] Exception while handling key command M1DOWN

The key thing to note is that the same [send] is mentioned twice here. i'm not sure why, but this may be a deadlock on the [send] trigger lock. I would have thought the relaying of the message would be enough decoupled that this would not be a problem, but maybe not.

Here's the simplified patch:
simpfied

The commit above fixes this problem and makes the test patches work. It was in fact a deadlock on the trigger_lock, caused by the [send] object's odd behavior of calling self.dest.obj.send() in its trigger method to pass the object along.

The fix pulls the "message forwarding" out of the locked trigger method and into a custom _send.