I have a problem regarding the ConnectTimeout from Indy 10's TIdTCPClient.
When setting the ConnectTimeout higher than 125ms the Connect() procedure will block the current thread for 125ms. If it is less than 125ms, it will block for the given time (e.g. it blocks for 30ms if the timeout is set to 30ms). In both cases, the connection is stable and I can transmit and receive data.
Why is the TIdTCPClient behaving like that?
IMHO the Connect() procedure should exit directly after the connection is successfully made and only block the full duration of the timeout if no connection can be opened.
Here's my code for monitoring the duration of the Connect() procedure.
The timer for calling TimerConnectTimer is set to 250ms.
I am using Lazarus v1.6.4 and Indy 10 under Windows 7 Professional.
procedure TForm1.FormCreate(Sender: TObject);
begin
timer := TEpikTimer.create(self);
timer.Clear;
end;
procedure TForm1.TimerConnectTimer(Sender: TObject);
begin
timer.Start;
client := TIdTCPClient.create();
logTime(0);
// Tested with values between 10ms and 1000ms
client.ConnectTimeout := SpinEdit1.Value;
try
logTime(1);
// choose ip and port for a running server
client.connect('192.168.2.51', 9912);
logTime(2);
except
end;
logTime(3);
try
client.Disconnect();
FreeAndNil(client);
except
end;
logTime(4);
timer.Clear;
end;
procedure TForm1.logTime(ch: integer);
begin
StringGrid1.Cells[0, ch] := FormatFloat('0.00', timer.Elapsed*1000);
end;
When setting the ConnectTimeout higher than 125ms the Connect() procedure will block the current thread for 125ms. If it is less than 125ms, it will block for the given time (e.g. it blocks for 30ms if the timeout is set to 30ms). In both cases, the connection is stable and I can transmit and receive data.
Why is the TIdTCPClient behaving like that?
That is hard to answer without knowing which version of Indy 10 you are using exactly, as the implementation of ConnectTimeout has changed over the years. But, in general:
If ConnectTimeout is set to IdTimeoutDefault or 0, IdTimeoutInfinite is used instead.
If Connect() is called in the main UI thread, TIdAntiFreeze is being used, and an infinite timeout is used, a hard-coded 2min timeout is used instead.
If any timeout is used, Connect() spawns a worker thread to connect the socket to the server, and then waits up to the timeout for the thread to terminate. If the timeout elapses before the thread terminates, Indy closes the socket and terminates the thread.
Assuming you are using a fairly up-to-date version of Indy 10 (at least SVN revision 5382 on Dec 14 2016, or later), and are not using TIdAntiFreeze, then on Windows only, the wait should exit immediately when the worker thread terminates, as Indy makes a single call to WaitForSingleObject() on the thread for the full timeout and exits as soon as WFSO exits.
If you are using TIdAntiFreeze, or are using Indy on a non-Windows platform, or are using a version of Indy 10 prior to SVN rev 5382, the wait calls IndySleep() (and if needed, TIdAntiFreeze.DoProcess()) in a loop at fixed intervals (125ms, or TIdAntiFreeze.IdleTimeOut, whichever is smaller) until the timeout is exceeded or the thread has terminated. In which case, there may be a slight delay before Connect() exits, as each sleep cycle has to complete before Connect() can check if the thread has terminated, and do so for every sleep interval within the overall connect timeout.
IMHO the Connect() procedure should exit directly after the connection is successfully made and only block the full duration of the timeout if no connection can be opened.
That is exactly what it currently does, on Windows at least. If the connection is successful, the thread terminates, and Connect() exits immediately (even if TIdAntiFreeze is used). The current sleep cycle is ended by the thread terminating itself. That is not the case on non-Windows platforms (for now, may be addressed in a future version).
Note that all of the above only applies when a timeout is used. If no timeout is used, no worker thread is used, either. Connect() simply connects the socket directly and lets the attempt block the calling thread until finished, whether successful or not.
Here's my code for monitoring the duration of the Connect() procedure.
I would suggest something more like this instead:
procedure TForm1.TimerConnectTimer(Sender: TObject);
begin
try
timer.Start;
try
logTime(0);
client := TIdTCPClient.create();
try
// choose ip and port for a running server
client.Host := '192.168.2.51';
client.Post := 9912;
// Tested with values between 10ms and 1000ms
client.ConnectTimeout := SpinEdit1.Value;
logTime(1);
client.Connect();
logTime(2);
client.Disconnect();
logTime(3);
finally
FreeAndNil(client);
end;
logTime(4);
finally
timer.Clear;
end;
except
end;
end;
Related
Before I shutdown the Indy TCPServer with Active:= False, I must signal all connections to abort what they doing and exit, because otherwise the server will be blocked and, with it, my application too.
From what I can think of, I need two things:
A way to send a Disconnect, because the thread may be blocked reading for commands.
A way to signal the thread to terminate, like Terminate; method of the TThread. I searched through server and I didn't found something similar. I know sending a Disconnect will throw an exception and the thread will exit, but maybe the thread is not reading and is busy doing something.
I tried this, but, of course, is not working and it corrupts my application and the system close it...
procedure TMyTcpServer.StopServer;
var List: TIdContextList;
I: Integer;
begin
List:= Contexts.LockList;
for I:= 0 to List.Count -1 do
TIdContext(List.Items[I]).Connection.Disconnect(False);
Contexts.UnlockList;
Active:= False;
end;
I want to download some large files (GB) from an FTP server.
The download of the first file always works. Then when trying to get the second file I get:
"Socket Error # 10038. Socket operation on non-socket."
The error is on 'Get'. After 'Get' I see these messages (via FTP status event):
Starting FTP transfer
Disconnecting.
Disconnected.
The code is like this:
{pseudo-code}
for 1 to AllFiles do
begin
if Connect2FTP then
begin
FTP.Get(Name, GzFile, TRUE, FALSE); <--- Socket operation on non-socket" error (I also get EIdConnClosedGracefully 'Connection Closed Gracefully' in IDE, F9 will resume execution without problems, but this is OK)
Unpack(GzFile); <--- this takes more than 60 seconds
end;
end;
if FTP.Connected
then FTP.Disconnect;
--
function Connect2FTP(FTP: TIdFTP; RemoteFolder: string; Log: TRichLog): Boolean;
begin
Result:= FTP.Connected;
if NOT Result then
begin { We are already connected }
FTP.Host := MyFTP;
FTP.Username:= usr;
FTP.Password:= psw;
TRY
FTP.Connect;
EXCEPT
on E: Exception DO
Result:= FTP.Connected;
if Result then FTP.ChangeDir(RemoteFolder);
end;
end;
Full code here: http://pastebin.com/RScj86R8 (PAS) or here https://ufile.io/26b54 (ZIP)
I think the problem appears after calling 'Unpack' which takes few minutes.
UPDATE: CONFIRMED: The problem appears after calling 'Unpack'. I removed the call and everything is fine. Pausing (sleep or break point) the program between downloads for a while (I think for more than 60 sec) will create the same problem.
FTP uses multiple socket connections, one for commands/responses, and separate connections for each transfer.
The most likely cause of the socket error is an FTP-unaware proxy/router/firewall sitting in between TIdFTP and the FTP server is closing the command connection after a short period of inactivity. During the Unpack() (or manual pause), no commands/responses are being transmitted on the command connection, it is sitting idle, and thus is subject to being closed by a timeout on such a proxy/router/firewall.
During a transfer, the command connection is sitting idle, no FTP commands/responses are being transmitted on it (unless you abort the transfer), until the transfer is complete. An FTP-unaware proxy/router/firewall may close the command connection prematurely during this time.
To avoid that, TIdFTP has a NATKeepAlive property that can enable TCP keep-alives on the command connection while it is sitting idle. This usually prevents premature closes.
However, when there is no transfer in prgress, TIdFTP disables TCP keep-alives on the command connection if NATKeepAlive.UseKeepAlive is True. TIdFTP uses TCP keep-alives only during transfers, with the assumption that you are not going to perform long delays in between FTP commands. If you need to delay for awhile, either close the FTP connection, or send an FTP command at regular intervals (such as calling TIdFTP.Noop() in a timer/thread).
Alternatively, you can try manually enabling TCP keep-alives after connecting to the server, and set NATKeepAlive.UseKeepAlive to False so TIdFTP will not automatically disable the keep-alives after each transfer, eg:
function Connect2FTP(FTP: TIdFTP; RemoteFolder: string; Log: TRichLog): Boolean;
begin
Result := FTP.Connected;
if not Result then
begin { We are not already connected }
FTP.Host := MyFTP;
FTP.Username:= usr;
FTP.Password:= psw;
try
FTP.Connect;
try
FTP.ChangeDir(RemoteFolder);
// send a TCP keep-alive every 5 seconds after being idle for 10 seconds
FTP.NATKeepAlive.UseKeepAlive := False; // False by default, but just in case...
FTP.Socket.Binding.SetKeepAliveValues(True, 10000, 5000);
except
FTP.Disconnect(False);
raise;
end;
except
Exit;
end;
Result := True;
end;
end;
Note that while most platforms support enabling TCP keep-alives on a per-connection basis (keep-alives are part of the TCP spec), setting keep-alive intervals is platform-specific. At this time, Indy supports setting the intervals on:
Windows 2000+ and WinCE 4.x+, for Win32 and .NET
Linux, when Indy is used in Kylix
Unix/Linux and NetBSD, when Indy is used in FreePascal.
Otherwise, OS default intervals get used, which may or may not be too large in value for this situation, depending on OS configuration.
At this time, the intervals are not customizable in Delphi FireMonkey, except for Windows.
If a particular platform supports setting custom TCP keep-alive intervals, but Indy does not implement them in SetKeepAliveValues(), you can use TIdFTP.Socket.Binding.SetSockOpt() instead to set the values manually as needed. Many platforms do support TCP_KEEPIDLE/TCP_KEEPINTVL or equivalent socket options.
I have an Indy Server TIdTCPServer which has 3 bindings for different ports. If I connect a client to those 3 ports, and then deactivate the server, it gets stuck in what appears to be a deadlock. No matter what I do, it won't respond to my click, it won't even report "not responding" to Windows. If I disconnect the client(s) before deactivating the server, everything goes just perfect. I mean "deactivating" as in Server.Active:= False;.
Has anyone else experienced this? What might be causing it? I have nothing happening in here which crosses over threads which could in turn cause a deadlock (for example GUI updates). I tried an Antifreeze component TIdAntiFreeze but no luck.
TIdTCPServer is a multi-threaded component. A deadlock during server deactivation means that one or more of its client threads is not terminating correctly. That usually means that your server event handlers are doing something they should not be doing, typically either catching and discarding Indy's internal exceptions to itself, synchronizing with the thread context that is busy terminating the server, or deadlocking on something else outside of Indy. Without seeing your actual code, there is no way to know for sure which is actually the case, but it is always user error that causes this kind of deadlock.
TIdAntiFreeze only affects Indy components that run in the context of the main thread. TIdTCPServer does not.
I added this code on Form.OnClose works good!
procedure TformSFTP.FormClose(Sender: TObject; var Action: TCloseAction);
var
iA : Integer;
Context: TidContext;
begin
if sftpServidorFTP.Active then
with sftpServidorFTP.Contexts.LockList do
try
for iA := Count - 1 downto 0 do
begin
Context := Items[iA];
if Context = nil then
Continue;
Context.Connection.IOHandler.WriteBufferClear;
Context.Connection.IOHandler.InputBuffer.Clear;
Context.Connection.IOHandler.Close;
if Context.Connection.Connected then
Context.Connection.Disconnect;
end;
finally
sftpServidorFTP.Contexts.UnlockList;
end;
if sftpServidorFTP.Active then
sftpServidorFTP.Active := False;
end;
I am troubleshooting a Delphi 7 Indy9 polling client. I have tried adding a TEvent with a waitforsingleobject and many other ways to disconnect gracefully. The error occurs in the readln. The error is usually an 'EIDConnection...not connected'. I have put a watch on it and the thread terminates. but the 'while' doesn't reevaluate the condition until the connection receives a msg from the server, so it just grinds at the readln until it receives a msg. So sometimes it disconnects gracefully but most times crashes. Is there a way to do this or do I just put a try...except around the readln and carry on...thanks in advance
procedure TReadingThread.Execute;
begin
while not Terminated and FConn.Connected do
begin
// read msg from server
Msg := FConn.ReadLn;
Synchronize(ReceiveLine);
end;
end;
I think you need to add some code to handle the Disconnect event. I had a similar problem to what you describe, and here's what I did (in this example, tcpServer is an instance of TIdTCPServer):
procedure TformRemoteControlWnd.tcpServerDisconnect(AContext: TIdContext);
(*
Connection is disconnected. Be careful, because this also gets called when
the app is shutting down while a connection is active, in which case
tcpServer may be gone already.
*)
begin
if not Application.Terminated and Assigned(tcpServer) then
begin
sbarStatus.SimpleText := 'TCP/IP Disconnected';
tcpServer.Tag := 0; // used to prevent rentrancy
end;
// shut down connection to stop thread from calling OnExecute event
try
AContext.Connection.Disconnect;
except
Sleep(0);
end;
end;
I have found the answer...Readln will wait indefinitely until it receives a carriage return. So the Thread sits at Readln until the server sends a message or the socket is disconnected (which causes the crash). In the Delphi compiler code, a comment was written in the OnDisconnect to trap the error using a try...except. So I just need to be careful to clean up before disconnecting the socket. I thought I could find a cleaner close method. Thanks for all the help.
I discovered that when setting the ConnectTimeoout property for a TIdHTTP component, it makes the requests (GET and POST) become about 120ms slower?
Why is this, and can I avoid/bypass this somehow?
Env: D2010 with shipped Indy components, all updates installed for D2010. OS is WinXP (32bit) SP3 with most patches...
My timing routine is:
Procedure DoGet;
Var
Freq,T1,T2 : Int64;
Cli : TIdHTTP;
S : String;
begin
QueryPerformanceFrequency(Freq);
Try
QueryPerformanceCounter(T1);
Cli := TIdHTTP.Create( NIL );
Cli.ConnectTimeout := 1000; // without this we get < 15ms!!
S := Cli.Get('http://127.0.0.1/empty_page.php');
Finally
FreeAndNil(Cli);
QueryPerformanceCounter(T2);
End;
Memo1.Lines.Add('Time = '+FormatFloat('0.000',(T2-T1)/Freq) );
End;
With the ConnectTimeout set in code I get avg. times of 130-140ms, without it's about 5-15ms ...
When ConnectTimeout is zero (and TIdAntifreeze is not in effect), Indy simply connects. Otherwise, TIdIOHandlerStack.ConnectClient calls DoConnectTimeout, which creates a new thread to do the connecting while the calling thread sleeps and processes TIdAntifreeze operations, waiting for the connection to be established. If there's not connection by the time the timeout elapses, it throws an exception.
Threads aren't free, and the calling thread will always sleep before checking whether the connection thread has accomplished its task. The default sleep duration is 125 ms. (To use something else, activate TIdAntifreeze and set its IdleTimeout property lower than 125.)