4

I'm trying to write a class that wraps around a serial port to read a sensor:

classdef sensor < handle
    properties
        Value
        s
    end

    methods
         function obj = sensor(port)
             obj.s = serial(port);
             obj.s.BytesAvailableFcn = @(o,e) obj.getData;

             fopen(obj.s);
         end
         function delete(obj)
             disp('called destructor');
             try
                 fclose(obj.s);
                 delete(obj.s);
             end
          end
          function getData(obj)
              obj.Value = fscanf(obj.s, '%d');
          end
     end
end

When I try to clear the workspace, the destructor never gets called:

>> foo = sensor('COM1');
>> clear foo % should disp() something!

Based on my previous experiences, there must still be a reference to foo. Turns out this is embedded in the serial port foo.s:

>> ports = instrfindall;
>> ports.BytesAvailableFcn
ans = 
    @(o,e)obj.getData

Once I clear out the BytesAvailableFcn, i.e.,

>> ports.BytesAvailableFcn = '';

and then clear all, I get my called destructor displayed.

How can I break this circular reference and get my destructor to get called? If the destructor doesn't get called, the serial port stays bound.

Community
  • 1
  • 1
Dang Khoa
  • 5,693
  • 8
  • 51
  • 80
  • 1
    the obvious solution is to explicitly call the `delete` method, but I'm hoping there's a better way to do this. – Dang Khoa Jun 12 '14 at 16:51

2 Answers2

2

The problem is not quite that you have a circular reference; MATLAB should in theory catch those. The issue is that the circular reference goes via Java, and MATLAB can't catch it.

In more detail - your serial object contains a Java object internally, which is the real thing that captures the connection to the serial port. When you set its callback, MATLAB sets the callback of the underlying Java object. If the callback is to a method of an object that contains the serial object as a property, then you have a circular reference that goes via the underlying Java object. When you call clear, MATLAB checks for circular references, and if they were all in only MATLAB it would catch them, and call the destructor - but it doesn't catch them as it goes via Java.

One solution is to explicitly call the destructor, and perhaps that's not too much trouble.

Another workaround might be to simply not have the callback as a method of the class - perhaps it could just be a regular function, as it doesn't really seem to need any information from the main object itself other than the reference to the serial object. If you would prefer to keep all the code in a single file, perhaps you could create it as an anonymous function (although be careful, as an anonymous function will capture the workspace that it's created in, which depending on where you create it may include a reference to the main object, in which case you'd have a circular reference again.

Edit: Rody's right - my apologies, I read the problem too fast and didn't notice that getData actually sets the Value property rather than just returning the data. Rody's solution in his answer may be an alternative workaround, but like he says - yuck. I'd just call delete manually.

Sam Roberts
  • 23,951
  • 1
  • 40
  • 64
  • I don't see how to create a function (that does not rely on some global state) without references to the object, if you want it to set the `Value` property...Calling `delete` manually would have my preference. – Rody Oldenhuis Jun 13 '14 at 13:45
1

Intriguing problem! :)

I found a workaround, but it's not going to be pretty.

  • You cannot use an anonymous function. That would capture the local workspace, which will contain a reference to obj. You'll have to use one level of indirection (to a Static method, otherwise, you'd have the same problem).
  • This static method can safely return a function handle. This function handle is to a function, which has to be passed the instrument object.
  • Setting the Value property is of course impossible without passing a reference to the object, so, you'll have to create a global state in the function, a variadic call signature, and a getter for the Value property.

I have the feeling I've over-engineered this a bit (it's Friday after all), so if anyone sees a simpler way, please correct me. Anyway, here's what I mean:

classdef sensor < handle

    properties
        s
    end        
    properties (Dependent)
        Value
    end

    methods

        function obj = sensor(port)
            obj.s = serial(port);

            % You cannot use function handle without implicitly passing obj into 
            % it. Instead, get a function handle from another function, one that 
            % does not have this problem:
            obj.s.BytesAvailableFcn = sensor.getGetData(obj.s);                

            fopen(obj.s);
        end

        function delete(obj)
            disp('called destructor');
            try %#ok<TRYNC>
                fclose(obj.s);
                delete(obj.s);
            end
        end

        % Use a getter for Value, so that whenever you query the Value property, 
        % you get the most recently read sensor data
        function V = get.Value(obj) %#ok<MANU>
            V = getData();
        end

    end

    % Use indirection to a Static method, to avoid referencing obj implicitly
    methods (Access = private, Static)
        function f = getGetData(S)
            f = @(o,e)getData(S);
        end
    end

end

% The actual sensor reader 
function data = getData(S)

    persistent port
    if nargin == 1
        port = S; return; end

    try 
        data = fscanf(port, '%d');
    catch ME
        % ...
    end

end

Just....yuck.

Rody Oldenhuis
  • 37,726
  • 7
  • 50
  • 96
  • yeesh. nice go at it! – Dang Khoa Jun 13 '14 at 14:59
  • Actually, this brings about a good point. Rather than using a persistent port, what if we use `instrfind('Type', 'serial', 'Port', nameOfPort)`, and pass around the name of the port (COM1, etc)? Then you'd instantiate an "anonymous" `serial` object in the class constructor. I have to think about that. – Dang Khoa Jun 13 '14 at 15:35
  • this way you don't even have to keep `s` as a property of the class. I wonder if that would help... – Dang Khoa Jun 13 '14 at 15:55
  • I was guessing that a persistent would have better performance. If you read the `Value` a lot, you don't want to do a search through *all* ports every time...OOP-wise it makes sense to have the class manage the serial port, in other words, having it as a property. But if MATLAB/Java interface has problems, and *this* is the workaround, then perhaps your idea is better :) – Rody Oldenhuis Jun 13 '14 at 16:17