Sometimes the way how anonymous methods work is just annoying...

Sometimes the way how anonymous methods work is just annoying...

type
ITest = interface
procedure Subscribe(const proc: TProc);
end;

TTest = class(TInterfacedObject, ITest)
private
fProc: TProc;
public
procedure Subscribe(const proc: TProc);
end;

procedure TTest.Subscribe(const proc: TProc);
begin
fProc := proc;
end;

procedure Main;
var
t: ITest;
p: TProc;
begin
t := TTest.Create;

t.Subscribe(procedure begin end);

p := procedure begin if t = nil then end;
end;

Leak or no leak? And if leak, why?

SPOILER:

Because the compiler backs all anonymous methods inside of the same routine into the same instance this code just creates a circular reference of t to itself. In the code it looks like t just takes a reference to that do nothing TProc and then we have another almost do nothing TProc that captures t. Both should go away at the end of the routine, right? Well, no. Since both anonymous methods are in the same instance t implicitly keeps itself alive. You can only solve this by adding t := nil before the end. This will clear the captured variable and resolve the circular reference. Bloody annoying!

I think this has been reported before (probably by myself because I trapped into that some years ago already) but I cannot find the entry right now.

I cannot think of a good solution given how anonymous methods and variable capturing works. In this case only one of them captures a variable but multiple anonymous methods can capture the same variables so they need to be in a place that both of them can access and kept alive as long as one of the anonymous methods are alive.

Comments

  1. Maybe you are talking about this entry: https://quality.embarcadero.com/browse/AP-120

    It's so annoying that I completely prohibited myself to use anonymous methods in Delphi at all. I hope it will be fixed someday.
    quality.embarcadero.com - Issue Navigator - Embarcadero Technologies

    ReplyDelete
  2. Christopher Wosinski Not exactly but probably caused by the same fact (all anonymous methods implemented by the same instance and thus causing circular references).

    FWIW I did not check how they are implemented in .Net, probably similar - thing is there a circular reference "bubble" will be handled by the GC whereas in Delphi a similar thing without reference from alive references are not.

    ReplyDelete
  3. How Swift does it would be the best place to look for solutions.

    Generally anonymous methods in Delphi don't play well with ARC. Cycles are not obvious - actually you should expect cycle rather than not and there is zero compiler help.

    Also capture lists are must have feature. quality.embarcadero.com - Log in - Embarcadero Technologies

    ReplyDelete
  4. Attila Kovacs The leak in your example is obvious, the one I posted is not and you have to know the implementation details to understand why it does that - which was my point.

    Weak does not help, you then are leaking the anonymous method for sure.

    ReplyDelete
  5. Attila Kovacs [weak] fProc is wrong because that is the one reference that should be strong in this case.

    ReplyDelete
  6. Ok, after a good night sleep (not really, still...) and clearer head...

    Stefan Glienke your code should not create any cycles. It does and this is clear flaw in compiler... if not reported already as bug it should be.

    ReplyDelete
  7. Also, when I said Swift is the place to look for solutions (behaviours) it was more directed to Delphi compiler folks than us.

    Swift has good patterns and ARC works well there. There is no reason why Delphi ARC flaws could not be purged and cleaned.

    If ARC bugs start becoming features then we are all doomed.

    ReplyDelete
  8. Attila Kovacs "Anon proc's wont be instantiated, copied, freed."

    Boom, they are - please learn about the implementation details first and then come back discussing things because without proper knowledge its moot.

    ReplyDelete
  9. Attila Kovacs It is not just a pointer. Anonymous methods are implemented as interfaces, that is why weak actually helps in this case and this is why fProc should not be weak because that is the reference you are storing for later usage.

    You can read more at stackoverflow.com - How are anonymous methods implemented under the hood?

    ReplyDelete
  10. Good solution = drop ARC and use a proper GC

    g,d&r :)

    ReplyDelete
  11. Attila Kovacs No, it does not work as designed...
    Hint... focus on the meaning of the word "cycle"...

    ReplyDelete

Post a Comment