delphiindyidhttpppl

Access Violation caused by Indy SSL component finalization (PPL / TTask)


I'm using Delphi version (Delphi 10.2u2)

My goal is to play with a web service to process multi-threaded GET, POST commands through Indy framework, nothing really complex and everything works fine until I close the application during a request.

To do so, I'm using TidHTTPClient to process commands and TIdSSLIOHandlerSocketOpenSSL to support traffic encryption.

For multithreading I'm using TTask from Parallel Programming Library (PPL). "I'm quite new in using this library with Delphi."

Here is a code sample:

procedure TForm3.Button1Click(Sender: TObject);
begin
TTask.Create(
              procedure
              var HTTP : TidHTTP;
                  SSL : TIdSSLIOHandlerSocketOpenSSL;
                  content : String;
              begin
                HTTP := TIdHTTP.Create(nil);

                SSL := TIdSSLIOHandlerSocketOpenSSL.create(nil);

                SSL.SSLOptions.Method := sslvSSLv23;

                HTTP.IOHandler       := SSL;
                HTTP.ConnectTimeout  := 20000;
                HTTP.ReadTimeout     := 60000;
                HTTP.HandleRedirects := true;

                try
                  {
                    If we close the application during the Get, it raise an exception.
                    This is because the SSL Library is freed before the get has the time to stop

                    finalization
                      UnLoadOpenSSLLibrary(); // called before get is over
                  }
                  Content := HTTP.Get('https://www.google.com/');

                  TThread.Synchronize(nil, procedure begin
                     memo1.text := Content;
                  end);
                except
                  // ...
                  on E : Exception do
                    TThread.Synchronize(nil, procedure begin
                      memo1.Text := 'Error';
                    end);
                end;

                SSL.Free;
                HTTP.Free;
  end).Start();
end;

If you compile and execute this code, you will see that everything just work fine until to decide to close the program during a request (GET, POST or whatsoever)

The error could vary depending of the precise moment you close the application during the HTTP Request. It would be an access violation, a Winsock2 (WSA...) error etc...

Here is an example of error: Access Violation

After a bit of searching, I found that the error was caused by the fact required libraries are freed before the running sub threads.

Indeed, because SSL / Winsock (especially) libraries are freed during the unfinished HTTP request, the continuity of the request will call some unavailable function and cause null pointer exception, access violation etc...

Before using TTasks I was using regular TThread's and everything was just working fine because on the application destroy event I was waiting until all my TThread's finished their task before letting the destruction process going more far.

As far as I know and I'm probably wrong, Delphi manage that part, but seems to do it after Units finalization.

The best solution for me would be to be able to control the state of my TTasks on the destruction event of my program like. Wait until all my TTasks finished their tasks.

BUT, when I can't find a way to do that without raising other kind of exception like "Operation Canceled"

procedure TForm3.FormDestroy(Sender: TObject);
begin
  ATask.Cancel;
  repeat
  if ATask.Wait(500) = false then begin
     CheckSynchronize();
  end;
 until (ATask = nil);
end;

Above example wont work...

I'm sure I'm doing something wrong, I could not find any help related to that subject elsewhere, Delphi PPL sounds to much new or my issue is really singular.

A big thanks in advance for your help.

I really want to use PPL for one of my biggest projects, I can't deploy an application with such error messages ^_^

Kind Regards,


Solution

  • Thank you for putting me on the way Remy. Here is the complete solution of the problem.

    procedure TForm3.FormDestroy(Sender: TObject);
    begin
      while not TTask.WaitForAll(FTasks, 1000) do
        CheckSynchronize();
    end;