Recent

Author Topic: Help needed with a thread code  (Read 2116 times)

ertank

  • Sr. Member
  • ****
  • Posts: 276
Help needed with a thread code
« on: August 03, 2018, 11:25:00 am »
Hello,

I am using Lazarus 1.8.4 and fpc 3.0.4 both installed using official DEB packages from lazarus web site. Target platform is Linux amd64.

I have a thread that I make a connection to a PostgreSQL database using zeoslib components. My main application is also making a connection to very same database. I have defined "-dUseCThreads" in my custom options and necessary thread units are in use by final executable.

My thread, I am getting Access violation that I failed to find the reason. Thread code is nothing too big so I am posting it all below:
Code: [Select]
unit uUserClean;

{$mode objfpc}{$H+}

interface

uses
  ZConnection,
  ZDataset,
  Classes,
  SysUtils;

type
  TUserClean = class(TThread)
  protected
    FDB: TZConnection;
    FqryUsers: TZReadOnlyQuery;
    FqryDropUser: TZQuery;

    procedure Execute; override;
    procedure Log(const Value: string);
  public
    LogDir: string;
    constructor Create(); reintroduce; overload;
    destructor Destroy(); override;
  end;

implementation

{ TUserClean }

constructor TUserClean.Create();
begin
  inherited Create(True);
  FreeOnTerminate := True;

  FDB := TZConnection.Create(nil);
  FDB.Protocol := 'postgresql';
  FDB.Database := 'pars';
  FDB.HostName := 'localhost';
  FDB.User := 'parsuser';
  FDB.Password := 'passwordforpars';

  FqryUsers := TZReadOnlyQuery.Create(nil);
  FqryUsers.Connection := FDB;
  FqryUsers.SQL.Text := 'select usename from pg_user where usename like ''pars.%'' and valuntil < current_timestamp';

  FqryDropUser := TZQuery.Create(nil);
  FqryDropUser.Connection := FDB;
  FqryDropUser.SQL.Text := 'DROP USER :username';
end;


destructor TUserClean.Destroy();
begin
  Log('TUserClean.Execute(): Thread finishing...');

  FqryUsers.Free();
  FqryDropUser.Free();
  FDB.Free();

  inherited;
end;


procedure TUserClean.Log(const Value: string);
var
  F: TextFile;
  Prefix: string;
  FileName: string;
begin
  Prefix := FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz    ', Now());
  FileName := LogDir + FormatDateTime('yyyy-mm', Now()) + '_thread.log';
  {$I-}
  AssignFile(F, FileName);
  if FileExists(FileName) then
    Append(F)
  else
    ReWrite(F);
  if IOResult <> 0 then Exit();

  try
    WriteLn(F, Prefix + Value);
  finally
    CloseFile(F);
  end;
end;


procedure TUserClean.Execute();
var
  I: Integer;
begin
  Log('TUserClean.Execute(): Running execute...');
  while not Terminated do
  begin
    Log('Looping...');
    try
      while not FDB.Connected do
      begin
        Log('Connecting database');
        try
          FDB.Connect();
        except
          on E: Exception do
          begin
            Log('Error: TUserClean.Create(): ' + E.Message);
            Exit();
          end;
        end;

        for I := 0 to 100 do
        begin
          if Terminated then Exit();
          Sleep(100);
        end;
        Log('Retrying database connection');
      end;

      if Terminated then Exit();

      if FqryUsers.Active then FqryUsers.Refresh() else FqryUsers.Open();
      if FqryUsers.RecordCount > 0 then
      begin
        Log('There are ' + FormatFloat('#,##0', FqryUsers.RecordCount) + ' users that needs to be removed from system.');
        FqryUsers.First();
        while not FqryUsers.Eof do
        begin
          Log('Removing user: ' + FqryUsers.Fields[0].AsString);
          FqryDropUser.ParamByName('username').AsString := FqryUsers.Fields[0].AsString;
          try
            FqryDropUser.ExecSQL();
          except
            on E: Exception do
            begin
              Log('Remove failed: ' + E.Message);
            end;
          end;
          if Terminated then Exit();
          FqryUsers.Next();
        end;
      end;

      Log('Sleeping until next check');
      for I := 0 to 100 do
      begin
        if Terminated then Exit();
        Sleep(100);
      end;
    except
      on E: Exception do
      begin
        Log('Error: ' + E.Message);
      end;
    end;
  end;
  Log('TUserClean.Execute(): Terminated');
end;


end.

Here is how I call it:
Code: [Select]
  Log('Starting user cleanup thread'); // Main application log routine. Writing in a different file than the thread log
  CleanupThread := TUserClean.Create(True);
  Log('Adjusting user cleanup thread parameters');
  CleanupThread.LogDir := LogDir;
  CleanupThread.Start();

And, here is my thread log file contents:
Code: [Select]
ek@greenkey:~/pars/logs$ head 2018-08_thread.log
==> 2018-08_thread.log <==
2018-08-03 12:03:19.878    TUserClean.Execute(): Running execute...
2018-08-03 12:03:19.879    Looping...
2018-08-03 12:03:19.879    Error: Access violation
2018-08-03 12:03:19.879    Looping...
2018-08-03 12:03:19.879    Error: Access violation
2018-08-03 12:03:19.879    Looping...
2018-08-03 12:03:19.879    Error: Access violation
2018-08-03 12:03:19.879    Looping...
2018-08-03 12:03:19.879    Error: Access violation
2018-08-03 12:03:19.879    Looping...

Looking at below lines in Execute block
Code: [Select]
    Log('Looping...');
    try
      while not FDB.Connected do
      begin
        Log('Connecting database');
By looking at error in log file, I would say "FDB" is not created. Otherwise log should contain "Connecting database" text in it.

I could not see what is my mistake here. "FDB" class is created when thread is created. It is a private variable so it should be used in all class' procedures/units.

I appreciate any help.

Thanks & regards,
Ertan
« Last Edit: August 03, 2018, 11:26:51 am by ertank »

Pascal

  • Hero Member
  • *****
  • Posts: 932
Re: Help needed with a thread code
« Reply #1 on: August 03, 2018, 12:11:26 pm »
Just an idea: FDB is created in main thread but used in other thread.
Try to create FDB and related objects in the Execute method.
Code: Pascal  [Select][+][-]
  1. procedure TUserClean.Execute();
  2. var
  3.   I: Integer;
  4. begin
  5.   FDB := TZConnection.Create(nil);
  6.   FDB.Protocol := 'postgresql';
  7.   FDB.Database := 'pars';
  8.   FDB.HostName := 'localhost';
  9.   FDB.User := 'parsuser';
  10.   FDB.Password := 'passwordforpars';
  11.  
  12.   FqryUsers := TZReadOnlyQuery.Create(nil);
  13.   FqryUsers.Connection := FDB;
  14.   FqryUsers.SQL.Text := 'select usename from pg_user where usename like ''pars.%'' and valuntil < current_timestamp';
  15.  
  16.   FqryDropUser := TZQuery.Create(nil);
  17.   FqryDropUser.Connection := FDB;
  18.   FqryDropUser.SQL.Text := 'DROP USER :username';
  19.  
  20.   Log('TUserClean.Execute(): Running execute...');
  21.   while not Terminated do
  22.   begin
  23.     Log('Looping...');
  24.     try
  25.       while not FDB.Connected do
  26.       begin
laz trunk x64 - fpc trunk i386 (cross x64) - Windows 10 Pro x64 (21H2)

ertank

  • Sr. Member
  • ****
  • Posts: 276
Re: Help needed with a thread code
« Reply #2 on: August 03, 2018, 12:34:36 pm »
Just an idea: FDB is created in main thread but used in other thread.
Is it only when Execute() runs within a new thread? And anything else is run in main application thread like TThread.Create?

Try to create FDB and related objects in the Execute method.
That actually is working. Thanks.

Pascal

  • Hero Member
  • *****
  • Posts: 932
Re: Help needed with a thread code
« Reply #3 on: August 03, 2018, 12:47:30 pm »
Just an idea: FDB is created in main thread but used in other thread.
Is it only when Execute() runs within a new thread? And anything else is run in main application thread like TThread.Create?
Yes, Execute() is the only procedure that is called inside the thread (and of course everything that is called from there)

Try to create FDB and related objects in the Execute method.
That actually is working. Thanks.
Pleased to here.

You should also dispose the objects (FDB , ...) at the end of the Execute() loop. (Consider disposing a not started thread!)
laz trunk x64 - fpc trunk i386 (cross x64) - Windows 10 Pro x64 (21H2)

 

TinyPortal © 2005-2018