• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1430
  • Last Modified:

Indy TIdAntiFreeze not working?

I have an existing client server project that I am converting to use TIdTCPClient and TIdTCPServer.  The "old" version used TClientSocket & TServerSocket with non-blocking sockets.  

The current problem is with the Server.  I have a TIdTCPServer and TIdAntiFreeze components on the main server form.  The Execute method is very simple (right now):

procedure TfmMain.IdTCPServer1Execute(AThread: TIdPeerThread);
  Client: PClientInfo;
  sTaskType: string;
  if (AThread.Terminated) or (not AThread.Connection.Connected) then EXIT;
  Client := PClientInfo(AThread.Data);
  Client.LastAction := Now;
  TaskMsgStrList.CommaText := AThread.Connection.ReadLn;  // <== input to fmMain.TaskMsgStrList
  AThread.Connection.WriteLn(TaskMsgStrList.CommaText);   // <== just ECHO it
  sTaskType := LowerCase(TaskMsgStrList.Values['TaskType']);
  if sTaskType = 'login' then
    Client.UserName := TaskMsgStrList.Values['UserName'];

The problem is that in between communications the Server form is completely unresponsive.  It appears to be "blocked" on the ReadLn statement above.

I thought that that was what the TIdAntiFreeze component was supposed to prevent.
  • 5
  • 3
1 Solution
Antifreeze is for client use only.

Each Execute is running in the context of AThread, so it does not need antifreeze. Antifreeze basically calls application.processmessages on the client side so that if you use a tcpclient in the mainVCL it will not prevent you cancelling things.

Generally if your code is sitting tight on the readln then it has not received an end of line character in the data.
you may find that the antifreeze is having an adverse reaction in the server code.
also. remember you are in a thread context.

 TaskMsgStrList.CommaText := AThread.Connection.ReadLn;  // <== input to fmMain.TaskMsgStrList

This should be in a syncronize if it is talking to Visual VCL code like a memo etc etc.
PHenningsenAuthor Commented:
In all my recent reading about Indy, I never once read that "Antifreeze is for client use only".  But it makes sense.  Anyhow removing it had no effect.

What do you mean by "it has not received an end of line character in the data"?  So far, I'm just using ReadLn & WriteLn.  And I find no place to specify an EOL character.

Further, after the Client sends a msg, the Server responds to my mouse click, but then the Server is still "fubar"...  To the extent that I have to use Task Manager to kill Delphi (yes, Delphi, not the app it was debugging) before continuing.

Re: TaskMsgStrList.CommaText := AThread.Connection.ReadLn.  TaskMsgStrList is intended to be part of "thread local storage".  So my next question is: How do I extend TIdPeerThread to add it?  Where might I find an example of doing that?

Phil Henningsen
Cloud Class® Course: Certified Penetration Testing

This CPTE Certified Penetration Testing Engineer course covers everything you need to know about becoming a Certified Penetration Testing Engineer. Career Path: Professional roles include Ethical Hackers, Security Consultants, System Administrators, and Chief Security Officers.

PHenningsenAuthor Commented:
Further on the Server being "unresponsive":  The Server has a TPageControl with 5 Pages or Tabs.  If I start the Server, and click on each tab BEFORE starting the Client, then all is well.

If I start the Server, then start the Client, then the Server goes unresponsive after the 1st transaction is processed.

Very wierd....
It is not weird at all. It is the reason that threads have the synchronize() method for talking to VCL code.
Each gui control has a property Handle:THandle, which binds it to the operating system to receive messages. To save resource. The handle property is only allocated on first use.

ie Memo1.Lines.Add('test')

if this is the first time you've used the memo then that is when the handle is created. if you access a VCL control from within a thread not using synchronize then the Handle is actually created using the thread process as the owner instead of the VCL. from then on your program just won't work properly.

You need to double buffer your server threads using critical sections and intermediary data.

Binding Session Objects.

Not really sure what PClientInfo is?
Typically in the OnConnect event of the server I would create a session object (userdefine) which contains all resources needed by a client session to do its job and
bind it to the thread with the thread Data property. ( I normally go the other way and have a threadsafe list of objects and do the lookup based on threadid)

ie  (sample only, not real)
TUserSession = class(TObject)
  property Thread:TIDPeerThread;
  property UserName:string;
  property Datamodule:string;
  property Privelages:TUserPrivelages

  property ClientInfo:PCLientInfo; // adding your stuff
  property TaskMsgList:TSTringList;
everything this object should be thread safe, and should not talk to the VCL.

in on execute find this object and make use of it.

in the OnDisconnect you free this object.

typically I dont build visual servers. (I have been using indy for 4 years for server based controls)  I build non-visual servers and write information to logs. if i want to see what is happening on the server then I pump out statistics to another client viewer on regular intervals or via messaging systems.

i think the main key here with your issue is the binding of Threaded code into the main VCL. its just a no- no.

One other note on ReadLn and WriteLn.

by using this method of retrieval you are opening your system up to buffer overrun attacks from hackers.

WriteLn('Hi') sends 'Hi'#13#10 over the socket

Readln will get 'Hi'#13#10 and finish the job returning 'Hi' as the result.

if someone conects to your server and does something like

while true do write('a');

Readln will never be able to complete its readln and your server will eventually run out of memory. This is a common past time of people who like to ruin software.
I tend to always use know size parameters.


to send a string 'Hello'
I do something like this


on the other side

ASize := ReadInteger;
if ASize < 1000000 (or some time of validation ) then
  AString := ReadString(ASize);

this is also good from a debugging point of view and results in a faster processing server because it does not have to calculate the length of the data. Essentially it is a PASCAL style string with the size byte first so that you don't need a delimiter at the end.

Hope this helps

PHenningsenAuthor Commented:
Thanks so much.  The light is beginning to dawn.

Since the Server's OnConnect procedure was:
procedure TfmMain.IdTCPServer1Connect(AThread: TIdPeerThread);
I pretty much assumed that I was operating in the context of TfmMain.  The culprit was (in ..Connect)
  AThread.Synchronize(RefreshClientListDisplay);  // ie the lack of "synchronize"

Could you expand a bit on what you meant by:
"You need to double buffer your server threads using critical sections and intermediary data."

And, do you mind if I leave this question open for a day before I award you all 500 points & an "A"?
I dont mind about the points at all.

By double buffering I mean... don't talk to the GUI. Make your server non-visual (Dump the controls on a datamodule) Have it so that the threaded server portion adds information to a non-visual control. Create yourself a TCriticalSection (unit SyncObjs) and wrap this around any code which is shared among threads. Then instead of telling the GUI to refresh itself directly, have the GUI examine the server for properties. ie ClientList property, and update itself accordingly. You could use a ttimer or something to keep it simple. The mecansim I uses is thread safe Observation, using a Signal & Slot pattern (To hard to explain here).

I usually think that code should be nicely de-coupled. Make the GUI look at the non-gui, this way it is easy to split the non-gui into a service object.

fuzzy logic code ... :)

FMyTCPServerDM = datamodule with TidTCPServer and all other stuff. with a critical section etc etc.

FMyTCPServerDM.Lock =
  FCS.Acquire; (Local owned critical section)

FMyTCPServerDM.UnLock =
  FCS.Release; (Local owned critical section)


  if FLastClientListUpdateTime <> FMyTCPServerDM.FLastClientUpdateTime then
       Memo1.Lines.Assign(FMyTCPServerDM.ClientList) // Clientlist is a locally owned TStringList;
    FLastClientUpdateTime := FMyTCPServerDM.FLastClientUpdateTime;

//    set FMyTCPServerDM.FLastClientUpdateTime := now; in OnConnect and OnDisconnect;
//    update FMyTCPServerDM..ClientList in OnConnect and OnDisconnect;


Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

  • 5
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now