Controlling callback re-entrancy

I’d like to welcome guest blogger Malcolm Lidierth of King’s College London. Malcolm is well known in the Matlab-Java community for his jcontrol utility. Some months ago, I mentioned his work on another File Exchange contribution, MUtilities when I discussed transparent Matlab figure windows. Today, Malcolm discusses one of his lesser-known but extremely important isMultipleCall utility.

Every now and again, a very simple bit of code turns out to be more useful than the author initially imagined. Something I have repeatedly used is the isMultipleCall function which I posted to MATLAB Central’s File Exchange a year or so ago.

The isMultipleCall function uses fully-documented pure-MATLAB to extend the control that can be achieved over callbacks.

Here was the problem: I had a modular system built in MATLAB which allowed third-party developers to add their own plugins. I wanted a mechanism to force the dismissal (“bail-out”) of a callback even when the Interruptible property of the parent object was set to ‘on’. Such callback re-entrancy issues are common for rapidly-firing events, and debugging and fixing them is usually not easy.

The callback’s dismissal code would need to be fast because it might be called many dozens of times, e.g. in a WindowButtonMotion callback. An obvious approach was to check the function call stack using MATLAB’s dbstack function. Although, at first, this seemed likely to be too slow, profiling showed it was not – taking < 40µsec per call – and within a WindowButtonMotion callback in a real GUI, I could not perceive any slowing of the code.

Here is the function:

function flag=isMultipleCall()
  flag = false; 
  % Get the stack
  s = dbstack();
  if numel(s)< =2
    % Stack too short for a multiple call
    return
  end
 
  % How many calls to the calling function are in the stack?
  names = {s(:).name};
  TF = strcmp(s(2).name,names);
  count = sum(TF);
  if count>1
    % More than 1
    flag = true; 
  end
end

With isMultipleCall invoked from another function (see note below), dbstack will return a structure with a minimum of 2 elements – the first relating to isMultipleCall itself and the second to the calling function. So with numel(s) <= 2, there can be no multiple calls and we can return false immediately thus saving time in doing any further testing. For numel(s) > 2 we simply check to see whether the calling functions referenced in s(2) appears anywhere else on the stack. If it does, then we return true; otherwise false.

Then, in our callback code we simply use:

if isMultipleCall();  return;  end

If this line is placed first in the callback function code, it essentially mimics the behavior that you might expect after setting the Interruptible property of the event firing object to ‘off’. Adding a drawnow() at the end of the callback will ensure that any waiting callbacks in the queue are dismissed:

function MyCallback(hObj, EventData)
  % Quick bail-out if callback code is called before another has ended
  if isMultipleCall();  return;  end
 
  ...  % do some actual callback work here
  drawnow();
end

There are several ways in which isMultipleCall can extend the standard MALAB functionality. First, by moving isMultipleCall reference from the first line of the callback we can create both an interruptible and an uninteruptible code block, e.g.

function MyCallback(hObj, EventData)
 
  %Code Block 1
  ...
 
  if isMultipleCall();  return;  end
 
  %Code Block 2
  ...
 
  drawnow();
end

Second, as isMultipleCall controls the callbacks – not the objects that trigger them – we can individually control the callbacks of objects which fire multiple events. That is particularly useful with Java components, which gives a third extension – isMultipleCall can be used in any function: not just the callbacks of standard MATLAB components, but also of Java or COM components.

Finally, as the callback, not the object is being controlled, we can control a callback that may be shared between multiple objects e.g. a menu component and a toolbar button.

Not bad for 13 lines of code.

Note: isMultipleCall must be called from a function, not from a string in the callback property.

Do you have any other favorite mechanism for controlling callback re-entrancy? If so, please post a comment.

Categories: Guest bloggers, GUI, Low risk of breaking in future versions

Tags: , , , ,

Bookmark and SharePrint Print

16 Responses to Controlling callback re-entrancy

  1. Mikhail says:

    My approach is instead of calling

    varargout = func( varargin )

    inside of callback, use following syntax:

    varargout = func_queue( @func, varargin )

    where function

    func_queue()

    is responsible for interrupting if called multiple times and always caching last varargin so that it is not getting lost.

  2. Arda says:
    persistent returnFlag
    if ~isempty(returnFlag)
       return
    end
    returnFlag=1;
    ...do something
    returnFlag=[];

    6 lines :)

  3. @Arda – that’s certainly faster by about 10-fold, but would be less easy to maintain e.g. with multiple return points. Perhaps the challenge should be to achieve this with less than one line in the callback!

    • Arda says:

      @Malcolm, it was just a joke. Sorry for the misunderstanding. Yet the code i wrote above does not work as i suspected..

    • @Arda
      Your code worked for me in a WindowButtonMotionFcn relacing …doSomething with

       line(rand(1,10),rand(1,10));
      drawnow();

      That example simulates the effect of setting ‘Interruptible’ to ‘off’ and ‘BusyAction’ to ‘cancel’ – but via the callback rather than the object. If isMultipleCall() took up a large proportion of the execution time, an approach like this would be far better – the processor overhead for your code is tiny. Another tack is to clear the callback property at the start and reset it at the end of the callback routine – but if any error or ^C occurs while its cleared how would you reset it?

    • @Malcolm, you cannot clear the callback property value from within an executing callback, AFAIK. At least, it didn’t work a few releases ago and since it made sense I did not try again lately. The property appears to change, but then reverts to its previous value when the callback ends.

    • Malcolm Lidierth says:

      @Yair
      That surprises me. You certainly can with a figure WIndowButtonMotion callback The code below runs just once after using set(figH, ‘WindowButtonMotionFcn’, @MyCallback).

      function MyCallback(hObject, EventData)
      set(hObject, 'WindowButtonMotionFcn', []);
      disp('In Callback');
      return
      end
    • Christos Oreinos says:

      I also used a solution almost similar to Arda’s (using a persistent variable). After reading this post I opted for the isMultipleCall() because, like Malcolm very very wisely says, I had problems when internal callback errors (which were not fatal in my case) or Ctrl+C stopped the callback that I was controlling for recursive call. In this case the callback could not be run again unless I explicitly cleared all variables.

  4. Arda says:

    By the way, if the handle has properties ‘BusyAction’ and ‘Interruptable’ (like figure windows, uicontrols, etc.), setting ‘cancel’ and ‘off’ is always a choice. That should do the same…

  5. Michele says:

    This utility is very interesting, but due to the use of ‘dbstack’ it wouldn’t work in compiled applications.
    Do you know of any easy workaround for that?
    Thank you very much

    • @Michele – you could store a temporary flag in a persistent variable or the handle’s ApplicationData/UserData, or a variant of these. For example:

      % Variant1
      function myCallbackFcn1(hObject,eventData,varargin)
         persistent inCallback
         if ~isempty(inCallback),  return;  end
         inCallback = true;
         try
             % do something useful here
         catch
             % error trapping here
         end
         pause(0.001); drawnow;  % give all other events a chance to bail out above
         inCallback = [];
      end  % myCallbackFcn1
       
      % Variant2
      function myCallbackFcn2(hObject,eventData,varargin)
         inCallback = getappdata(hObject,'inCallback');
         if ~isempty(inCallback),  return;  end
         setappdata(hObject,'inCallback',true);
         try
             % do something useful here
         catch
             % error trapping here
         end
         pause(0.001); drawnow;  % give all other events a chance to bail out above
         setappdata(hObject,'inCallback',[]);
      end  % myCallbackFcn2
    • Michele says:

      @Yair – Thank you very much for the quick reply, I’ll definitely give it a try.

  6. Rafael says:

    This is very interesting. But how would you handle the case when you don’t want to bail out of the callback if it’s already executing, but you want to wait instead for the first instance to finish? The obvious solution of putting a polling loop does not work because the loop will keep Matlab from finishing the execution of the first instance. I can’t seem to find a solution to that problem!
    Thanks!

    • @Rafael – if the callback’ed object supports the BusyAction and Interruptable properties, then setting these would be the easiest choice. Otherwise, you could preserve the callback parameters in some persistent data struct (e.g., a cell array) and set a timer object to run the callback function programmatically in a second or two.

  7. Rafael says:

    Thanks a lot for the answer @Yair!
    I’m not sure your solution of using a timer to re-execute the function will work for me. I have thought of that solution for a while, but let’s say you have two things that must happen in a particular order, and only the first one is the one that must be re-executed by the timer. How do I hold the execution of the second process until the first one has for sure executed? I get back to the polling loop problem! Would I have to convert all of my processes into timers that are constantly firing to check if it’s OK to execute in the right order? My whole system would be come a nightmare if I have to do that!

  8. Elliott says:

    This has been a really useful post and has really helped out a Matlab GUI that I have.

    I’m confused on one count though, and I’m sure this is just my lack of understanding of how Matlab runs behind the scenes. Why does this not work with a figure’s ResizeFcn callback? I would really like to have a custom resize, but it needs to be safe against re-entrancy. Currently, I’d handle this with a timer, but I’d rather not have to use that.

    Is there a way to get this to work with the ResizeFcn?

    Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *