0

Tried to describe most of my problem in the title, and I mostly did.

Basically, I've made my own little TCP Server with Indy 10 in Lazarus. All it does is it accepts packets in form of bytes that contain a certain char representing a letter from English alphabet. I'm reading these bytes with the Contexts' IOHandler like this:

procedure TServerSideForm.OnExecuteServer(Context: TIdContext);
var
  IO: TIdIOHandler;
  keyPressed: char;
begin
  //
  IO := Context.Connection.IOHandler;

  if not(IO.InputBufferIsEmpty) then
  begin
    LogForm.LogToForm('Recieving a packet from ' + Context.Binding.IP + '(' + Context.Binding.PeerIP + ')');

    keyPressed := IO.ReadChar;
    AddKeyToAppropriateClient(keyPressed, Context.Binding.IP);
  end;

  IndySleep(10);
  //
end; 

And this works perfectly.

However, I've got another form that has a TTabControl in it, which has a tab for each user connected to my server, there is also a TMemo in each tab. Tabs and memos are created at runtime and the function that does that, does not throw any exceptions.

Here's how the AddTab() function from my other form looks like (for creating tabs and memos described above):

procedure TConnectionsForm.AddTab(IP: string);
var
  newMemo: TMemo;
begin
  ConnectionTabs.Tabs.Add(IP);
  currTabIndx := ConnectionTabs.TabIndex;

  newMemo := TMemo.Create(ConnectionsForm);

  newMemo.AnchorSide[akTop].Side:=asrTop;
  newMemo.AnchorSide[akTop].Control:=ConnectionTabs;
  newMemo.BorderSpacing.Top:=2;

  newMemo.AnchorSide[akBottom].Side:=asrBottom;
  newMemo.AnchorSide[akBottom].Control:=ConnectionTabs;
  newMemo.BorderSpacing.Bottom:=2;

  newMemo.AnchorSide[akLeft].Side:=asrLeft;
  newMemo.AnchorSide[akLeft].Control:=ConnectionTabs;
  newMemo.BorderSpacing.Left:=2;

  newMemo.AnchorSide[akRight].Side:=asrRight;
  newMemo.AnchorSide[akRight].Control:=ConnectionTabs;
  newMemo.BorderSpacing.Right:=2;

  newMemo.Anchors := [akTop, akBottom, akLeft, akRight];

  newMemo.Parent := ConnectionTabs;
  newMemo.Visible:=true;

  newMemo.Lines.Add(IP);

  SetLength(connectionMessagesArr, Length(connectionMessagesArr)+1);
  connectionMessagesArr[Length(connectionMessagesArr)-1] := newMemo;

  ShowOnly(currTabIndx);
end; 

It seems to be working fine, I've tested it multiple times on its own.

But when I run that function from my OnConnect function for my TIdTCPServer the server process seems to freeze until some other event, like OnExecute, for example, happens. When OnExecute does execute, the messages that have been accepted by the process are also executed. For example, if my program freezes, and while it's frozen, I will try to minimize one of my forms and move some other form somewhere else on the screen, they will do that after the program unfreezes.

Here's my own OnConnect function:

procedure TServerSideForm.OnConnectServer(Context: TIdContext);
begin
  LogForm.LogToForm(Context.Binding.IP + ' has connected to the server (' + Context.Binding.PeerIP + ')');

  //ConnectionsForm.AddTab(Context.Binding.PeerIP); // Boom
  //ConnectionsForm.ConnectionTabs.Tabs.Add('!!'); // Boom
end;  

As I've also stated in the title, any kind of interaction with TTabControls tabs makes the program freeze. ConnectionsForm.ConnectionTabs.Tabs.Add('!!'); also makes the program freeze (here, ConnectionsTabs is my TTabControl variable inside my form called ConnectionsForm).

I really have no idea to what is going on in here, so some help would really be appreciated.

Michael
  • 548
  • 6
  • 23
  • I've done a little bit more testing. It appears that most of the runtime form-manipulations of my secondary `ConnectionsForm` cause a freeze. – Michael Aug 20 '17 at 06:20
  • Another thing, I'm new to using Indy10 and I'm guessing something is not thread-safe in my code. As I've read on other sources, accessing UI elements in `OnSomething` events is not thread-safe. So I have to synchronize my function calls somehow, but what is the best way in my situation? – Michael Aug 20 '17 at 06:37
  • `TIdTCPServer` is a multi-threaded component, its events run in worker threads, so you MUST synchronize with the main UI thread when touching the GUI. You can use Indy's `TIdSync` or `TIdNotify` class for that (in this situation, I would use `TIdNotify` instead of `TIdSync`, since there is no need for the thread to wait on the UI to return), or use FPC's `TThread.Synchonize()` or `TThread.Queue()` method. – Remy Lebeau Aug 25 '17 at 01:28
  • 1
    On a side note, you shouldn't be using `InputBufferIsEmpty` in the manner you showed. Call `ReadChar` unconditionally, let it block until a character actually arrives, then do the rest of your code: `procedure TServerSideForm.OnExecuteServer(Context: TIdContext); var keyPressed: char; begin keyPressed := Context.Connection.IOHandler.ReadChar; LogForm.LogToForm('Recieving a packet from ' + Context.Binding.IP + '(' + Context.Binding.PeerIP + ')'); AddKeyToAppropriateClient(keyPressed, Context.Binding.IP); end;` Just make sure `LogToForm()` and `AddKeyToAppropriateClient()` are thread-safe. – Remy Lebeau Aug 25 '17 at 01:28
  • Thanks for the information @RemyLebeau. I've already made several changes to my program since I posted this question, some of do include using `TThread`s. And my functions are thread-safe. – Michael Aug 25 '17 at 03:47

1 Answers1

3

Well, yes, my program was really thread-unsafe.

Solved the problem by adding my little Sync class:

type
  TMySync = class(TIdSync)
  protected
    procedure DoSynchronize; override;
  public
    context: TIdContext;
  end;

And doing all my TTabControl shenanigans in its DoSyncronize overridden function.

procedure TMySync.DoSynchronize;
begin
  LogForm.LogToForm(Context.Binding.IP + ' has connected to the server (' + Context.Binding.PeerIP + ')');

  ConnectionsForm.AddTab(Context.Binding.PeerIP);
end; 

And OnConnect has been changed to:

procedure TServerSideForm.OnConnectServer(Context: TIdContext);
var
  sync: TMySync;
begin
//
  sync := TMySync.Create;
  try
    sync.context := Context;
    sync.Synchronize;
  finally
    Sync.Free;
  end;
end;     

P.S. Did an analogous this to my OnDisconnect function.

Everything seems to work for now.

Michael
  • 548
  • 6
  • 23