Recent

Author Topic: Why is this interface delegation causing memory leak?  (Read 4031 times)

zamronypj

  • Full Member
  • ***
  • Posts: 133
    • Fano Framework, Free Pascal web application framework
Why is this interface delegation causing memory leak?
« on: August 29, 2019, 12:17:37 pm »
Suppose I have following codes

File TalkerIntf.pas

Code: Pascal  [Select][+][-]
  1. unit TalkerIntf;
  2.  
  3. interface
  4.  
  5. {$MODE OBJFPC}
  6.  
  7. type
  8.  
  9.     ITalker = interface
  10.         procedure say() ;
  11.     end;
  12.  
  13. implementation
  14. end.
  15.  

File TalkerImpl.pas

Code: Pascal  [Select][+][-]
  1. unit TalkerImpl;
  2.  
  3. interface
  4.  
  5. {$MODE OBJFPC}
  6.  
  7. uses
  8.  
  9.     TalkerIntf;
  10.  
  11. type
  12.  
  13.     TTalker = class(TInterfacedObject, ITalker)
  14.     public
  15.         procedure say();
  16.     end;
  17.  
  18. implementation
  19.  
  20.     procedure TTalker.say();
  21.     begin
  22.         writeln('Hello');
  23.     end;
  24.  
  25. end.
  26.  

File DelegateTalkerImpl.pas

Code: Pascal  [Select][+][-]
  1. unit DelegateTalkerImpl;
  2.  
  3. interface
  4.  
  5. {$MODE OBJFPC}
  6.  
  7. uses
  8.  
  9.     TalkerIntf;
  10.  
  11. type
  12.  
  13.     TDelegateTalker = class(TInterfacedObject, ITalker)
  14.     private
  15.         fActualTalker : ITalker;
  16.     public
  17.         constructor create(const talker : ITalker);
  18.         destructor destroy(); override;
  19.  
  20.         property talker : ITalker read fActualTalker implements ITalker;
  21.     end;
  22.  
  23. implementation
  24.  
  25.     constructor TDelegateTalker.create(const talker : ITalker);
  26.     begin
  27.         fActualTalker := talker;
  28.     end;
  29.  
  30.     destructor TDelegateTalker.destroy();
  31.     begin
  32.         fActualTalker := nil;
  33.         inherited destroy();
  34.     end;
  35.  
  36. end.
  37.  
  38.  

and program memleak.pas

Code: Pascal  [Select][+][-]
  1. program memleak;
  2.  
  3. {$MODE OBJFPC}
  4.  
  5. uses
  6.  
  7.     TalkerIntf,
  8.     TalkerImpl,
  9.     DelegateTalkerImpl;
  10.  
  11. var
  12.     talker : ITalker;
  13.  
  14. begin
  15.     talker := TDelegateTalker.create(TTalker.create());
  16.     talker.say();
  17. end.
  18.  
  19.  

If you compile

Code: Bash  [Select][+][-]
  1. $ fpc -gh memleak.pas
  2. $ ./memleak
  3.  

heaptrc will report memory leak.

Code: [Select]
Hello
Heap dump by heaptrc unit
2 memory blocks allocated : 64/64
0 memory blocks freed     : 0/0
2 unfreed memory blocks : 64
True heap size : 32768
True free heap : 32384
Should be : 32448
Call trace for block $00007FA0D7846180 size 32
$000000000040020F
Call trace for block $00007FA0D78460C0 size 32

Why and how to avoid memory leak in above interface delegation scenario?

I use FPC 3.0.4

Update

It seems the only work around is to remove implements keyword and do delegation manually. Following code does not suffer from memory leak.

Code: Pascal  [Select][+][-]
  1. unit DelegateTalkerImpl;
  2.  
  3. interface
  4.  
  5. {$MODE OBJFPC}
  6.  
  7. uses
  8.  
  9.     TalkerIntf;
  10.  
  11. type
  12.  
  13.     TDelegateTalker = class(TInterfacedObject, ITalker)
  14.     private
  15.         fActualTalker : ITalker;
  16.     public
  17.         constructor create(const talker : ITalker);
  18.         destructor destroy(); override;
  19.  
  20.         procedure say();
  21.     end;
  22.  
  23. implementation
  24.  
  25.     constructor TDelegateTalker.create(const talker : ITalker);
  26.     begin
  27.         fActualTalker := talker;
  28.     end;
  29.  
  30.     destructor TDelegateTalker.destroy();
  31.     begin
  32.         fActualTalker := nil;
  33.         inherited destroy();
  34.     end;
  35.  
  36.     procedure TDelegateTalker.say();
  37.     begin
  38.         fActualTalker.say();
  39.     end;
  40. end.
  41.  

Is that bug?
« Last Edit: August 29, 2019, 01:18:44 pm by zamronypj »
Fano Framework, Free Pascal web application framework https://fanoframework.github.io
Apache module executes Pascal program like scripting language https://zamronypj.github.io/mod_pascal/
Github https://github.com/zamronypj

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: Why is this interface delegation causing memory leak?
« Reply #1 on: August 29, 2019, 01:00:31 pm »
Because TInterfacedObject, unlike TObject, DOES do some work. You need to call inherited!
Code: Pascal  [Select][+][-]
  1.     constructor TDelegateTalker.create(const talker : ITalker);
  2.     begin
  3.         inherited create;
  4.         fActualTalker := talker;
  5.     end;
The work that is done is setting up reference counting.

Note that even if you derive from TObject directly, you still must always call inherited, because this is implementation detail.
In your case, it is simply a mistake.
« Last Edit: August 29, 2019, 01:04:06 pm by Thaddy »
Specialize a type, not a var.

zamronypj

  • Full Member
  • ***
  • Posts: 133
    • Fano Framework, Free Pascal web application framework
Re: Why is this interface delegation causing memory leak?
« Reply #2 on: August 29, 2019, 01:12:48 pm »
@Thaddy

No, it does not fix memory leak. Only removing implements keyword and do manual delegation fix it.



« Last Edit: August 29, 2019, 01:21:09 pm by zamronypj »
Fano Framework, Free Pascal web application framework https://fanoframework.github.io
Apache module executes Pascal program like scripting language https://zamronypj.github.io/mod_pascal/
Github https://github.com/zamronypj

Thaddy

  • Hero Member
  • *****
  • Posts: 14204
  • Probably until I exterminate Putin.
Re: Why is this interface delegation causing memory leak?
« Reply #3 on: August 29, 2019, 01:45:14 pm »
Then you had two bugs, not one....
Specialize a type, not a var.

zamronypj

  • Full Member
  • ***
  • Posts: 133
    • Fano Framework, Free Pascal web application framework
Re: Why is this interface delegation causing memory leak?
« Reply #4 on: August 29, 2019, 02:48:06 pm »
If you take a look at TInterfacedObject source code, it does not define new constructor other than TObject constructor which actually does nothing.
So adding inherited create() or not, it basically same thing.  Real problem is  use implements keyword
Fano Framework, Free Pascal web application framework https://fanoframework.github.io
Apache module executes Pascal program like scripting language https://zamronypj.github.io/mod_pascal/
Github https://github.com/zamronypj

jamie

  • Hero Member
  • *****
  • Posts: 6090
Re: Why is this interface delegation causing memory leak?
« Reply #5 on: August 29, 2019, 10:44:17 pm »
I haven't done a lot of interface work however, wouldn't be obvious you need to call the destructor at some point? I don't see that in any of the sample code presented here?
The only true wisdom is knowing you know nothing

zamronypj

  • Full Member
  • ***
  • Posts: 133
    • Fano Framework, Free Pascal web application framework
Re: Why is this interface delegation causing memory leak?
« Reply #6 on: August 30, 2019, 12:06:44 am »
I haven't done a lot of interface work however, wouldn't be obvious you need to call the destructor at some point? I don't see that in any of the sample code presented here?

It uses COM interface, so it should automatically be freed when reference count is zero.
Fano Framework, Free Pascal web application framework https://fanoframework.github.io
Apache module executes Pascal program like scripting language https://zamronypj.github.io/mod_pascal/
Github https://github.com/zamronypj

ASerge

  • Hero Member
  • *****
  • Posts: 2222
Re: Why is this interface delegation causing memory leak?
« Reply #7 on: August 30, 2019, 04:06:59 am »
Lets see detail in Test proc:
Code: Pascal  [Select][+][-]
  1. {$IFDEF FPC}{$MODE OBJFPC}{$ENDIF}
  2. {$APPTYPE CONSOLE}
  3.  
  4. type
  5.   ITalker = interface(IInterface)
  6.   ['{C6512C47-A2ED-40E7-A3AE-C74EF5E71071}']
  7.     procedure Say;
  8.   end;
  9.  
  10.   TTalker = class(TInterfacedObject, ITalker)
  11.   public
  12.     procedure Say;
  13.   end;
  14.  
  15.   TDelegateTalker = class(TInterfacedObject, ITalker)
  16.   strict private
  17.     FActualTalker: ITalker;
  18.   public
  19.     constructor Create(const ATalker: ITalker);
  20.     property Talker: ITalker read FActualTalker implements ITalker;
  21.   end;
  22.  
  23. constructor TDelegateTalker.Create(const ATalker: ITalker);
  24. begin
  25.   inherited Create;
  26.   FActualTalker := ATalker;
  27. end;
  28.  
  29. procedure TTalker.Say;
  30. begin
  31.   Writeln('TTalker.Say');
  32. end;
  33.  
  34. {-$DEFINE NOLEAK}
  35. procedure Test;
  36. var
  37.   DelegateTalker: {$IFDEF NOLEAK}IInterface{$ELSE}ITalker{$ENDIF};
  38.   Talker: ITalker;
  39. begin
  40.   DelegateTalker := TDelegateTalker.Create(TTalker.Create);
  41.   Talker := (DelegateTalker as ITalker);
  42.   Talker.Say;
  43. end;
  44.  
  45. begin
  46.   {$IFDEF DCC} ReportMemoryLeaksOnShutdown := True; {$ENDIF}
  47.   Test;
  48.   Readln;
  49. end.
When you define a variable as ITalker, the newly created object is not returned, but only the field that implements it. As a result, a newly created object is leaked.
Delphi's behavior is the same.

zamronypj

  • Full Member
  • ***
  • Posts: 133
    • Fano Framework, Free Pascal web application framework
Re: Why is this interface delegation causing memory leak?
« Reply #8 on: August 30, 2019, 05:51:24 am »
@ASerge

Thank you for the explanation.

This behavior should be documented.
« Last Edit: August 30, 2019, 09:48:47 am by zamronypj »
Fano Framework, Free Pascal web application framework https://fanoframework.github.io
Apache module executes Pascal program like scripting language https://zamronypj.github.io/mod_pascal/
Github https://github.com/zamronypj

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1312
    • Lebeau Software
Re: Why is this interface delegation causing memory leak?
« Reply #9 on: August 30, 2019, 07:50:59 pm »
This behavior should be documented.

Delphi's documentation says this:

Implementing Interfaces by Delegation

Quote
The implements directive allows you to delegate implementation of an interface to a property in the implementing class. For example:

Code: Pascal  [Select][+][-]
  1. property MyInterface: IMyInterface read FMyInterface implements IMyInterface;

declares a property called MyInterface that implements the interface IMyInterface.

The implements directive must be the last specifier in the property declaration and can list more than one interface, separated by commas. The delegate property:

- Must be of a class or interface type.
- Cannot be an array property or have an index specifier.
- Must have a read specifier. If the property uses a read method, that method must use the default register calling convention and cannot be dynamic (though it can be virtual) or specify the message directive.

The class you use to implement the delegated interface should derive from System.TAggregatedObject.

Note the last sentence!

Does the leak go away if you derive TTalker from TAggregatedObject (or TContainedObject) instead of from TInterfacedObject?
« Last Edit: August 30, 2019, 07:55:31 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

ASerge

  • Hero Member
  • *****
  • Posts: 2222
Re: Why is this interface delegation causing memory leak?
« Reply #10 on: August 30, 2019, 08:52:35 pm »
Does the leak go away if you derive TTalker from TAggregatedObject (or TContainedObject) instead of from TInterfacedObject?
Of course not, I described the problem, it will not change. And it is easy to check.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1312
    • Lebeau Software
Re: Why is this interface delegation causing memory leak?
« Reply #11 on: August 30, 2019, 09:58:27 pm »
Of course not, I described the problem, it will not change.

Why not?  If the inner object is aggregated and delegates its reference counting to the outer object, then I would think it shouldn't matter which object gets returned by the implements clause.  Did you actually try it?

And it is easy to check.

Not for someone (me) who doesn't have a working compiler at the moment.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

ASerge

  • Hero Member
  • *****
  • Posts: 2222
Re: Why is this interface delegation causing memory leak?
« Reply #12 on: August 30, 2019, 10:22:04 pm »
Did you actually try it?
Certainly. Write your own version to make it clear what delegates to what.

PascalDragon

  • Hero Member
  • *****
  • Posts: 5446
  • Compiler Developer
Re: Why is this interface delegation causing memory leak?
« Reply #13 on: August 31, 2019, 11:46:30 am »
Interface delegation has a few details that need to be done right to avoid memory leaks.
First of the class instance the interface is delegated to needs to forward it's IInterface implementation to the container class (which is what TAggregatedObject does).
Then you should not store the contained class as an interface, but instead as a class instance to avoid a circular reference.

I'll use the example by ASerge as it's in one unit:

Code: Pascal  [Select][+][-]
  1. {$IFDEF FPC}{$MODE OBJFPC}{$ENDIF}
  2. {$APPTYPE CONSOLE}
  3.  
  4. type
  5.   ITalker = interface(IInterface)
  6.   ['{C6512C47-A2ED-40E7-A3AE-C74EF5E71071}']
  7.     procedure Say;
  8.   end;
  9.  
  10.   TTalker = class(TAggregatedObject, ITalker)
  11.   public
  12.     procedure Say;
  13.   end;
  14.  
  15.   TDelegateTalker = class(TInterfacedObject, ITalker)
  16.   strict private
  17.     FActualTalker: TTalker;
  18.     function GetTalker: ITalker;
  19.   public
  20.     constructor Create;
  21.     destructor Destroy; override;
  22.     property Talker: ITalker read GetTalker implements ITalker;
  23.   end;
  24.  
  25. constructor TDelegateTalker.Create;
  26. begin
  27.   inherited Create;
  28.   FActualTalker := TTalker.Create(Self);
  29. end;
  30.  
  31. destructor TDelegateTalker.Destroy;
  32. begin
  33.   FActualTalker.Free;
  34.   inherited;
  35. end;
  36.  
  37. function TDelegateTalker.GetTalker: ITalker;
  38. begin
  39.   Result := FActualTalker;
  40. end;
  41.  
  42. procedure TTalker.Say;
  43. begin
  44.   Writeln('TTalker.Say');
  45. end;
  46.  
  47. {-$DEFINE NOLEAK}
  48. procedure Test;
  49. var
  50.   DelegateTalker: {$IFDEF NOLEAK}IInterface{$ELSE}ITalker{$ENDIF};
  51.   Talker: ITalker;
  52. begin
  53.   DelegateTalker := TDelegateTalker.Create {$IFNDEF NOLEAK}as ITalker{$ENDIF};
  54.   Talker := (DelegateTalker as ITalker);
  55.   Talker.Say;
  56. end;
  57.  
  58. begin
  59.   {$IFDEF DCC} ReportMemoryLeaksOnShutdown := True; {$ENDIF}
  60.   Test;
  61.   Readln;
  62. end.

In this case it doesn't matter whether NOLEAK is defined or not (though for some reason the compiler barfs if there is no as ITalker in case of NOLEAK; I'll have to take a look at that).

It's important to note that interface delegation and interface injection don't work well together except one really knows what one is doing (and even then it can be frickle), so one should decide for one or the other.

zamronypj

  • Full Member
  • ***
  • Posts: 133
    • Fano Framework, Free Pascal web application framework
Re: Why is this interface delegation causing memory leak?
« Reply #14 on: September 02, 2019, 05:06:29 am »
The problem with PascalDragon's solution is TDelegateTalker becomes tightly-coupled with TTalker class. To me, this is defeat the purpose of interface.

Code: Pascal  [Select][+][-]
  1.     constructor TDelegateTalker.Create;
  2.     begin
  3.       inherited Create;
  4.       FActualTalker := TTalker.Create(Self);
  5.     end;
  6.  
He cannot use constructor dependency injection because, when TTalker is inherited from TAggregatedObject, it requires  instance of IUnknown interface during object construction which will be used to manage lifetime of aggregated object, so TDelegateTalker must be created first, before TTalker can be created. the easiest solution is to move is TTalker creation inside TDelegateTalker thus introduce tight couple between them.
Fano Framework, Free Pascal web application framework https://fanoframework.github.io
Apache module executes Pascal program like scripting language https://zamronypj.github.io/mod_pascal/
Github https://github.com/zamronypj

 

TinyPortal © 2005-2018