SchneiderInfosystems / FB4D

Open Source Library for connecting Firebase by Delphi VCL/FMX
https://www.schneider-infosys.ch/DelphiBlog/de/#FB4D
Apache License 2.0
175 stars 51 forks source link

Can't release IFirestoreDatabase inside thread #145

Closed arvanus closed 1 year ago

arvanus commented 1 year ago

BuildVersion = 493 Delphi 11.2

I'm using FB4D inside a rest server managed by Horse framework where each call will use a specific Firebase Firestore. My sample code works fine in the main thread, but when I run in another thread I can't release the IFirestoreDatabase reference. Here is a simple code that simulates the server:

program testcase;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  classes,
  FB4D.Firestore,
  FB4D.Interfaces,
  FB4D.Authentication,
  System.Threading;

procedure run;
var
  fAuth: IFirebaseAuthentication;
  fDatabase: IFirestoreDatabase;
begin
  fAuth := TFirebaseAuthentication.Create('YOUR API KEY');
  fDatabase := TFirestoreDatabase.Create('YOUR PROJECT ID', fAuth);
  fAuth.SignOut;
  writeln('almost');
  fDatabase := nil;
  writeln('there');
end;

begin
  writeln('OK:');
  run;
  writeln('NOT OK:');
  TTask.Create(run).start;//here is the problem
  readln;
end.

Expected behavior I believe I could release the fDatabase object when runing everything inside a thread

Thank you for this great project and help!

SchneiderInfosystems commented 1 year ago

This is an interesting problem, but fortunately the root cause is not in the FB4D code. To my knowledge, it is a problem of the TThreadclass that is not yet known. See this situation:

program testcase;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  classes,
  System.Threading;

type
  TMyClass = class
    procedure OnTerminate(Sender: TObject);
  end;

procedure run;
var
  MyThread: TThread;
  MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;
  MyThread := TThread.Create(true);
  MyThread.OnTerminate := MyClass.OnTerminate;
  writeln('almost');
  MyThread.Free;
  writeln('there');
end;

{ TMyClass }

procedure TMyClass.OnTerminate(Sender: TObject);
begin
  writeln('OnTerminate');
end;

begin
  writeln('OK:');
  run;
  writeln('NOT OK:');
  TTask.Create(run).start;//here is the problem
  readln;
end.

TThread is hanging within TThread.ShutdownThread when attaching an OnTerminate method. We should report this as RSP to Embarcadero in the Quality Central.

Do you have access to Quality Central and can you do this for me with this example and name the RSP here? I would then vote for the new RSP.

In FB4D I see in this situation no possibility to solve the problem without memory leak with a workaround.

Keep in mention for further Development. Inside a thread you should use always the synchronous methods because the Synchronize Call in the asynchronous methods are not working between two back ground threads. But this is here not the root case. I just want give you a hint for the next steps.

gorepj commented 1 year ago

-->FWIW I prefer to use the Omni Thread Library by Primož Gabrijelčič.  For my threading requirements.  Its an absolute masterpiece and highly recommended. Not sure if it suffers from the same TThread problem cited below.

arvanus commented 1 year ago

Created https://quality.embarcadero.com/browse/RSP-39975 Thanks!

arvanus commented 1 year ago

Just to reference a solution directly here, you need to lock the main thread in another way, l like this:

program testcase;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils,
  classes,
  System.Threading;

type
  TMyClass = class
    procedure OnTerminate(Sender: TObject);
  end;

procedure run;
var
  MyThread: TThread;
  MyClass: TMyClass;
begin
  MyClass := TMyClass.Create;
  MyThread := TThread.Create(true);
  MyThread.OnTerminate := MyClass.OnTerminate;
  writeln('almost');
  MyThread.Free;
  writeln('there');
end;

procedure run2;
begin
  readln;
end;

{ TMyClass }

procedure TMyClass.OnTerminate(Sender: TObject);
begin
  writeln('OnTerminate');
end;

begin
  writeln('OK:');
  run;

  writeln('NOT OK:');
  TTask.Create(run).start;//here is the problem, MyThread.Free takes forever
//  readln; //hang
  TTask.Create(run2).start; //don't hang!
end.