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;
Related
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;
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.)
My application is a tcp/ip server, with main thread created only once & listening all the time. When new client connects, the main thread creates the new thread of TClientThread type. There is however no list of running Client threads, as that would make my app a bit complicated... is there any way to execute "terminate" method on all the threads, even if the thread is busy (in my case "busy" means it's waiting for the data, where the timeout set is about 30 sec ... so I have to kill it anyway, without waiting.)?
The simple closing application seems not to run "terminate" method on the threads, which ends up with memory leaks reported by FastMM...
Memory leaks on shutdown are nothing to worry about - going to the trouble of freeing memory before returning control to the operating system is a waste of time and needlessly slows down application exit. All you really need to do is ensure that all data has been saved, and all interprocess handles (such as semaphores and mutexes) correctly released, and exit away.
For notifying clients, the best you can do would be a strategy somewhat like this:
Add all client-handling threads to some list somewhere (with suitable locking on creation, destruction and iteration)
Make client threads remove themselves from the list upon termination, and have the last item removed from the list set an event (manual reset event, e.g. TEvent in SyncObjs) if the server is shutting down
Introduce polling (e.g. select or equivalent with a timeout) or other kind of interruption (e.g. SO_RCVTIMEO / SO_SNDTIMEO) in what would otherwise be long-running blocking routines, monitoring the Terminated property
On shutdown, lock the list and iterate through it, calling Terminate, and then wait for the event to be signaled; of course, the listening socket which adds items to the list should be closed and known to be closed before iterating through the list
Sounds like this article may help
What you'll see if you click that link:
Using Semaphores in Delphi, Part 2:
The Connection Pool
By: Cary Jensen
Abstract: Semaphores are used to
coordinate multiple threads and
processes. That semaphores provide
multiple threads with simultaneous
access to a shared resource is
highlighted by the
TFixedConnectionPool class described
in this article.
I use a KillThreadList: TList global.
I monitor it in my thread as:
while (Not Terminated) do
begin
inc(Inker);
if (WaitForSingleObject(FTick, finterval) = WAIT_TIMEOUT) then
Begin
if Inker >= 10 then
Begin
ProcessTables;
Inker := 0;
sleep(1000);
End;
if KillThreadList.Contains(ThreadID) = True then Terminate;
End;
end;
I also test for the KillThreadList in my processes to let me opt out of them before completion, where safe to do so.
I pass the OnTerminate event out to the Main thread and remove the ThreadID from the KillList there. I use this model extensively and it has not failed me yet.
procedure TfrmProcessQualcommLocations.OnTerminateThread;
var
ThreadID : Cardinal;
i : integer;
aStatusBar :TStatFrame;
begin
ThreadID := (Sender as Tthread).ThreadID;
for i := 0 to StatusBarList.Count -1 do
Begin
if StatusBarList.Items[i].ThreadID = ThreadID then
Begin
aStatusBar := StatusBarList.Items[i];
KillThreadList.Extract(ThreadID);
StatusBarList.Extract(aStatusBar);
aStatusBar.Free;
break;
End;
End;
self.Refresh;
end;
In the case above, I am also removing some GUI stuff.
Hope that helps.
SpringerRider