Mega Search
23.2 Million


Sign Up

Make a donation  
IdTCPClient seems to "hang" waiting for response [Edit]  
News Group: embarcadero.public.delphi.internet.winsock

The following code seems to work, most of the time.  But there is a problem.  If the third party server is sending nothing the code appears to hang - waiting.

{code}
function TTSPayForm.ReadDPSMessage: IXMLDocument;
const
  cStart = ' -1 then Break;
         end;
         StartPos := IndyMax(InputSize-(Length(Term)-1), Length(cStart));
         IdTCPClient1.IOHandler.CheckForDisconnect(True, True);
         IdTCPClient1.IOHandler.CheckForDataOnSource(IdTimeoutDefault);
      until False;
   except
      on E : Exception do
      begin
         ShowMessage('DPS connection dropped.');
      end;
   end;
   // IDTCPClient1.IOHandler.
   sXmlStrm := TMemoryStream.Create;
   try
      IdTCPClient1.IOHandler.ReadStream(sXmlStrm, TermPos + Length(Term), False);
      sXmlStrm.Position := 0;
      // Memo1.Text := ReadStringFromStream(sXmlStrm, -1, Indy8BitEncoding);
      sXmlStrm.Position := 0;
      Result := TXMLDocument.Create(nil);
      Result.LoadFromStream(sXmlStrm);
  finally
    sXmlStrm.Free;
  end;
end;

{code}

The code runs within a timer.  But the timer is "hangs" while the TCPClient is waiting for a message from a third party "server".  Which it will not get until something happens on the hardware that server is connected to.  The server is connected to an EFTPOS pinpad.  

Sometimes it is necessary to cancel the transaction.  But the cancellation message cannot be sent from the software until the TCPClient gets another message.  As soon as that happens the cancellation message is sent.

I can't, for the life of me, figure some way to enable the Waitfor request to be interrupted.  In other words - if there is nothing there then the TCPClient should stop waiting.

Prior to the call the following code executes:

{code}
      if IdTCPClient1.IOHandler.InputBufferIsEmpty then
      begin
         if not IdTCPClient1.IOHandler.CheckForDataOnSource(10) then
            Exit;
      end;
      DPSMessage := ReadDPSMessage;
{code}

Do I need to clear the input buffer AFTER a successful read?

Any ideas?

Thanks

I've set the code to track on a log file.  The following is the contents of the file.

 PRESENT/INSERT 1:18:13 p.m.  Line 1055
//Text1[1]/nodePRESENT/INSERT 1:18:13 p.m. Line 1067
/Text2[1]/node()OR SWIPE CARD 1:18:13 p.m. line 1150
 TRANS. CANCELLED 1:18:13 p.m.  Line 1055
//Text1[1]/nodeTRANS. CANCELLED 1:18:13 p.m. Line 1067
AR TRANS CANCELLED 1:18:51 p.m. Line 914

What I find very strange is that the time doesn't change from 'PRESENT/INSERT' to 'TRANS. CANCELLED'.  Even though I counted something like 30 seconds.  It's as if something is 'turning off" the timer/clock.  As you can see 'AR TRANS CANCELLED' is logged at 38 seconds later.

I have looked for anything that could have turned off the timer and I couldn't see anything.  But the times in the log file are from the PC's clock.  Using "Now".

Alan

Edited by: Alan Jeffery on Dec 27, 2014 4:20 PM

Vote for best question.
Score: 0  # Vote:  0
Date Posted: 27-Dec-2014, at 4:26 PM EST
From: Alan Jeffery
 
Re: IdTCPClient seems to "hang" waiting for response  
News Group: embarcadero.public.delphi.internet.winsock
Alan wrote:

> I can't carry out the processing inside a thread as the code needs
> to access the UI.

A worker thread can access the UI, it just needs to sync with the main thread 
in order to access the UI safely.

> Sometimes the server response requires buttons be displayed and/or clicked.

All the more reason why the socket code should be moved to a worker thread.

> You may see that I've spotted that the clock seems to be disabled if
> there is no response from the TCPClient.

The clock is not stopped/disabled.  You are simply not accessing the clocik 
in a timely manner because your code is blocking.

--
Remy Lebeau (TeamB)

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 27-Dec-2014, at 4:52 PM EST
From: Remy Lebeau (TeamB)
 
Re: IdTCPClient seems to "hang" waiting for response  
News Group: embarcadero.public.delphi.internet.winsock
> {quote:title=Remy Lebeau (TeamB) wrote:}{quote}
> Alan wrote:
> 
> > The following code seems to work, most of the time.  But there is
> > a problem.  If the third party server is sending nothing the code
> > appears to hang - waiting.
> 
> Indy uses infinite timeouts by default.  You can either set the IOHandler.ReadTimeout 
> property to a non-infinte interval, or pass a non-infinite value to the ATimeout 
> parameter of TIdIOHandler.WaitFor().  That will cause Indy to raise an exception 
> if the timeout elapses.
> 
> > The code runs within a timer.
> 
> Then you should not be doing a looped read.  If the entire data is not available 
> immediately, exit the function and let the timer call it again later, eg:
> 
> {code}
> const
>   cStart = '   cEnd = '';
> 
> procedure Timer(Sender: TObject);
> var
>   Term: TIdBytes;
> begin
>   Term := ToBytes(cEnd);
>   IdTCPClient1.IOHandler.CheckForDataOnSource(10);
>   while IdTCPClient1.IOHandler.InputBuffer.IndexOf(Term) <> -1 do
>     DPSMessage := ReadDPSMessage;
> end;
> 
> function TTSPayForm.ReadDPSMessage: IXMLDocument;
> var
>   sXml: string;
>   sXMLStrm : TMemoryStream;
> begin
>   Result := nil;
> 
>   try
>     sXml := IdTCPClient1.IOHandler.ReadLn(cEnd, Indy8BitEncoding) + cEnd;
>   except
>     on E : Exception do
>     begin
>       ShowMessage('DPS connection dropped.');
>       Exit;
>     end;
>   end;
> 
>   // Memo1.Text := sXml;
> 
>   sXmlStrm := TMemoryStream.Create;
>   try
>     WriteStringToStream(sXmlStrm, sXml, Indy8BitEncoding);
>     sXmlStrm.Position := 0;
>     Result := TXMLDocument.Create(nil);
>     Result.LoadFromStream(sXmlStrm);
>   finally
>     sXmlStrm.Free;
>   end;
> end;
> {code}
> 
> Otherwise, move the reading to a worker thread and get rid off the timer 
> completely.  Let the thread loop and block, and notify the main thread whenever 
> a new XML message arrives.
> 
> > But the timer is "hangs" while the TCPClient is waiting for a message from 
> a third party "server".
> 
> That is because Indy operations are blocking, and you are performing them 
> in the context of the main UI thread, where they do not belong.
> 
> > I can't, for the life of me, figure some way to enable the Waitfor
> > request to be interrupted.  In other words - if there is nothing there
> > then the TCPClient should stop waiting.
> 
> The only options are to specifying a read timeout, or close the socket.
> 
> > Prior to the call the following code executes:
> 
> That only tells you if any data is waiting, but it does not tell you if a 
> complete message is waiting.
> 
> > Do I need to clear the input buffer AFTER a successful read?
> 
> No.
> 
> --
> Remy Lebeau (TeamB)

Remy

Thanks for that.  I can't carry out the processing inside a thread as the code needs to access the UI.  Sometimes the server response requires buttons be displayed and/or clicked.

I'll try your suggested fix and see how it goes.

You may see that I've spotted that the clock seems to be disabled if there is no response from the TCPClient.  I added the contents of a log file to the original message.

Regards.

Alan

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 27-Dec-2014, at 4:34 PM EST
From: Alan Jeffery
 
Re: IdTCPClient seems to "hang" waiting for response  
News Group: embarcadero.public.delphi.internet.winsock
Alan wrote:

> The following code seems to work, most of the time.  But there is
> a problem.  If the third party server is sending nothing the code
> appears to hang - waiting.

Indy uses infinite timeouts by default.  You can either set the IOHandler.ReadTimeout 
property to a non-infinte interval, or pass a non-infinite value to the ATimeout 
parameter of TIdIOHandler.WaitFor().  That will cause Indy to raise an exception 
if the timeout elapses.

> The code runs within a timer.

Then you should not be doing a looped read.  If the entire data is not available 
immediately, exit the function and let the timer call it again later, eg:

{code}
const
  cStart = ' -1 do
    DPSMessage := ReadDPSMessage;
end;

function TTSPayForm.ReadDPSMessage: IXMLDocument;
var
  sXml: string;
  sXMLStrm : TMemoryStream;
begin
  Result := nil;

  try
    sXml := IdTCPClient1.IOHandler.ReadLn(cEnd, Indy8BitEncoding) + cEnd;
  except
    on E : Exception do
    begin
      ShowMessage('DPS connection dropped.');
      Exit;
    end;
  end;

  // Memo1.Text := sXml;

  sXmlStrm := TMemoryStream.Create;
  try
    WriteStringToStream(sXmlStrm, sXml, Indy8BitEncoding);
    sXmlStrm.Position := 0;
    Result := TXMLDocument.Create(nil);
    Result.LoadFromStream(sXmlStrm);
  finally
    sXmlStrm.Free;
  end;
end;
{code}

Otherwise, move the reading to a worker thread and get rid off the timer 
completely.  Let the thread loop and block, and notify the main thread whenever 
a new XML message arrives.

> But the timer is "hangs" while the TCPClient is waiting for a message from 
a third party "server".

That is because Indy operations are blocking, and you are performing them 
in the context of the main UI thread, where they do not belong.

> I can't, for the life of me, figure some way to enable the Waitfor
> request to be interrupted.  In other words - if there is nothing there
> then the TCPClient should stop waiting.

The only options are to specifying a read timeout, or close the socket.

> Prior to the call the following code executes:

That only tells you if any data is waiting, but it does not tell you if a 
complete message is waiting.

> Do I need to clear the input buffer AFTER a successful read?

No.

--
Remy Lebeau (TeamB)

Vote for best answer.
Score: 0  # Vote:  0
Date Posted: 27-Dec-2014, at 3:13 PM EST
From: Remy Lebeau (TeamB)