View Issue Details

IDProjectCategoryView StatusLast Update
0027662LazarusLCLpublic2020-04-15 19:10
ReporterOndrej Pokorny Assigned ToZeljan Rikalo  
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionfixed 
PlatformLinux / Gtk2 
Product Version1.5 (SVN) 
Summary0027662: TGtk2WidgetSet.AppProcessMessages and Application.Idle(True) fail to call CheckSynchronize a second time
DescriptionIf you are in Application.ProcessMessages/Application.Idle(True) loop that was called from Synchronize, CheckSynchronize is not called and so the execution stays in the loop infinitely.

I tested Win32, OSX (Darwin) and Gtk2. Win32 and Darwin work properly, Gtk2 fails to call CheckSynchronize.
Steps To ReproduceCall TestThread in a LCL application.

type
  TMyThread = class(TThread)
  private
    fCounter: Integer;
    fFinished: Boolean;

    procedure StartNew;
  protected
    procedure Execute; override;
  public
    constructor Create(aCounter: Integer);

    property Terminated;
  end;

{ TMyThread }

constructor TMyThread.Create(aCounter: Integer);
begin
  inherited Create(False);
  fCounter := aCounter;
end;

procedure TMyThread.Execute;
begin
  Sleep(50);
  Synchronize(StartNew);
  fFinished := True;
end;

procedure TMyThread.StartNew;
var
  xThread: TMyThread;
begin
  if fCounter < 2 then
  begin
    xThread := TMyThread.Create(fCounter+1);
    xThread.FreeOnTerminate := False;

    while not xThread.fFinished do
    begin
      //dead-lock here in 2nd thread -> CheckSynchronize is not called
      Application.ProcessMessages;
      Application.Idle(True);
    end;
    xThread.Free;
  end;
end;

procedure TestThread;
var
  xThread: TThread;
begin
  xThread := TMyThread.Create(0);
  xThread.FreeOnTerminate := True;
end;
Additional InformationA quick solution would be to call CheckSynchronize directly in AppProcessMessages (as it is done e.g. in Win32):

procedure TGtk2WidgetSet.AppProcessMessages;

  function PendingGtkMessagesExists: boolean;
  begin
    {$IFDEF USE_GTK_MAIN_OLD_ITERATION}
    Result:=(gtk_events_pending<>0) or LCLtoGtkMessagePending;
    {$ELSE}
    Result := g_main_context_pending(g_main_context_default) or
      LCLtoGtkMessagePending;
    {$ENDIF}
  end;

var
  vlItem : TGtkMessageQueueItem;
  vlMsg : PMSg;
  i: Integer;
begin
  vlMsg:=nil;//ONDREJ
  repeat
    // send cached LCL messages to the gtk
    //DebugLn(['TGtk2WidgetSet.AppProcessMessages SendCachedLCLMessages']);
    SendCachedLCLMessages;

    // let gtk handle up to 100 messages and call our callbacks
    i:=100;

    if not FGtkTerminated then
    begin
      {$IFDEF USE_GTK_MAIN_OLD_ITERATION}
      while (gtk_events_pending<>0) and (i>0) do
      begin
        if FGtkTerminated then
          break;
        gtk_main_iteration_do(False);
        dec(i);
      end;
      {$ELSE}
      while g_main_context_pending(g_main_context_default) and (i>0) do
      begin
        if FGtkTerminated then
          break;
        if not g_main_context_iteration(g_main_context_default, False) then
          break;
        dec(i);
      end;
      {$ENDIF}
    end;

    //DebugLn(['TGtk2WidgetSet.AppProcessMessages SendCachedGtkMessages']);
    // send cached gtk messages to the lcl
    SendCachedGtkMessages;

    // then handle our own messages
    while not Application.Terminated do begin
      fMessageQueue.Lock;
      try
        // fetch first message
        vlItem := fMessageQueue.FirstMessageItem;
        if vlItem = nil then break;

        // remove message from queue
        if vlItem.IsPaintMessage then begin
          //DebugLn(['TGtk2WidgetSet.AppProcessMessages Paint: ',DbgSName(GetLCLObject(Pointer(vlItem.Msg^.hwnd)))]);
          // paint messages are the most expensive messages in the LCL,
          // therefore they are sent after all other
          if MovedPaintMessageCount<10 then begin
            inc(MovedPaintMessageCount);
            if fMessageQueue.HasNonPaintMessages then begin
              // there are non paint messages -> move paint message to the end
              fMessageQueue.MoveToLast(FMessageQueue.First);
              continue;
            end else begin
              // there are only paint messages left in the queue
              // -> check other queues
              if PendingGtkMessagesExists then break;
            end;
          end else begin
            // handle this paint message now
            MovedPaintMessageCount:=0;
          end;
        end;

        //DebugLn(['TGtk2WidgetSet.AppProcessMessages SendMessage: ',DbgSName(GetLCLObject(Pointer(vlItem.Msg^.hwnd)))]);
        vlMsg:=fMessageQueue.PopFirstMessage;
      finally
        fMessageQueue.UnLock;
      end;

      //debugln(['TGtk2WidgetSet.AppProcessMessages ',vlMsg^.Message,' ',LM_CHAR,' ',dbgsname(GetLCLObject(Pointer(vlMsg^.hwnd)))]);
      // Send message
      if vlMsg <> nil then
      begin
        try
          with vlMsg^ do SendMessage(hWND, Message, WParam, LParam);
        finally
          Dispose(vlMsg);
        end;
      end;
    end;

    // proceed until all messages are handled
  until (not PendingGtkMessagesExists) or Application.Terminated;

  //ONDREJ
  if (vlMsg=nil) and IsMultiThread then//no message was handled -> CheckSynchronize
    CheckSynchronize;
end;
TagsNo tags attached.
Fixed in Revision62989
LazTarget-
WidgetsetGTK 2
Attached Files

Relationships

related to 0027628 closedMichael Van Canneyt FPC TThread.Synchronize uses global lock and variables 

Activities

Zeljan Rikalo

2015-03-13 19:26

developer   ~0081925

Please attach example project. Also create patch if you have an idea howto fix it.

Ondrej Pokorny

2015-03-14 20:24

developer  

CheckSynchronize.zip (2,791 bytes)

Ondrej Pokorny

2015-03-14 20:24

developer  

gtk2widgetset.inc.patch (741 bytes)   
Index: gtk2widgetset.inc
===================================================================
--- gtk2widgetset.inc	(revision 48270)
+++ gtk2widgetset.inc	(working copy)
@@ -2300,6 +2300,7 @@
   vlMsg  : PMSg;
   i: Integer;
 begin
+  vlMsg:=nil;//ONDREJ
   repeat
     // send cached LCL messages to the gtk
     //DebugLn(['TGtk2WidgetSet.AppProcessMessages SendCachedLCLMessages']);
@@ -2384,6 +2385,10 @@
 
     // proceed until all messages are handled
   until (not PendingGtkMessagesExists) or Application.Terminated;
+
+  //ONDREJ
+  if (vlMsg=nil) and IsMultiThread then//no message was handled -> CheckSynchronize
+    CheckSynchronize;
 end;
 
 {------------------------------------------------------------------------------
gtk2widgetset.inc.patch (741 bytes)   

Zeljan Rikalo

2015-03-14 20:28

developer   ~0081965

I've seen related issue at fpc bug tracker. What will happen with 2.6.xx apps when checksynchronize is added into gtk2 ?

Ondrej Pokorny

2015-03-14 20:39

developer   ~0081967

Sample project + patch: here you go!

>> I've seen related issue at fpc bug tracker. What will happen with 2.6.xx apps when checksynchronize is added into gtk2 ?

The patch is just a "proposal". In Win32, CheckSynchronize is called in AppProcessMessages; in Gtk2, CheckSynchronize is called through another mechanism in Application.Idle(True) -> TGtk2WidgetSet.AppWaitMessage -> gtk_main_iteration_do(True), which fails in the current state.

All in all, the code works fine with my patch and I haven't noticed any problems with it. Performance isn't affected either. But if you found another (better) way how to call CheckSynchronize in this case, I would be happy with it!

+ Do you have a link to that bug report?

Zeljan Rikalo

2015-03-14 22:14

developer   ~0081969

You discussed there (or it was mailing list).

Ondrej Pokorny

2015-03-15 09:46

developer   ~0081975

Yes, that's true! I didn't realize that you meant my own bug report :)

+ Basically, an extra call of CheckSynchronize from AppProcessMessages should not cause any problems at all. It just checks if there are some methods pending from the Synchronize call and executes them.
And because AppProcessMessages has to be called from the main thread by design, there shouldn't be any problem at all.

Zeljan Rikalo

2015-03-15 09:54

developer   ~0081978

What was that bug id , so I can add it here as related bug report ?

Ondrej Pokorny

2015-03-15 14:32

developer   ~0081986

>> What was that bug id , so I can add it here as related bug report ?

27628: http://bugs.freepascal.org/view.php?id=27628

Zeljan Rikalo

2020-04-15 19:10

developer   ~0122165

Thanks for the patch and sorry for delayed commit, this patch just went below my radar :)
Please test and close if ok.

Issue History

Date Modified Username Field Change
2015-03-13 12:52 Ondrej Pokorny New Issue
2015-03-13 19:26 Zeljan Rikalo LazTarget => -
2015-03-13 19:26 Zeljan Rikalo Note Added: 0081925
2015-03-13 19:26 Zeljan Rikalo Assigned To => Zeljan Rikalo
2015-03-13 19:26 Zeljan Rikalo Status new => feedback
2015-03-14 20:24 Ondrej Pokorny File Added: CheckSynchronize.zip
2015-03-14 20:24 Ondrej Pokorny File Added: gtk2widgetset.inc.patch
2015-03-14 20:28 Zeljan Rikalo Note Added: 0081965
2015-03-14 20:39 Ondrej Pokorny Note Added: 0081967
2015-03-14 20:39 Ondrej Pokorny Status feedback => assigned
2015-03-14 22:14 Zeljan Rikalo Note Added: 0081969
2015-03-15 09:46 Ondrej Pokorny Note Added: 0081975
2015-03-15 09:54 Zeljan Rikalo Note Added: 0081978
2015-03-15 14:32 Ondrej Pokorny Note Added: 0081986
2015-03-15 14:56 Zeljan Rikalo Relationship added related to 0027628
2020-04-15 19:10 Zeljan Rikalo Status assigned => resolved
2020-04-15 19:10 Zeljan Rikalo Resolution open => fixed
2020-04-15 19:10 Zeljan Rikalo Fixed in Revision => 62989
2020-04-15 19:10 Zeljan Rikalo Widgetset GTK 2 => GTK 2
2020-04-15 19:10 Zeljan Rikalo Note Added: 0122165