Closing a Client thread gracefully - delphi

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.

Related

How to signal a Indy TCPServer connection thread to terminate?

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;

Indy10 ConnectTimeout minimal value

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;

Delphi prevent application shutdown

I am trying to prevent my application from being shutdown by windows.
The application is running on windows 8 and written in XE6.
I tried following code but it seems to be completely ignored. To test it I simply send "end task" to it through the task manager.
What I need is a way to let my application finish what its doing when the application is closed by the user, by the task manager of by a windows shutdown.
Normal closing is not a problem, this is handled by the FormCloseQuery event. But the other 2 methods I can't get to work. Until windows XP this was easy by catching the wm_endsession and the wm_queryendsession, starting from vista you need the use ShutDownBlockReasonCreate, which returns true but does not seems to work anyway.
procedure WMQueryEndSession(var Msg : TWMQueryEndSession); message WM_QUERYENDSESSION;
procedure WMEndSession(var Msg: TWMEndSession); message WM_ENDSESSION;
function ShutdownBlockReasonCreate(hWnd: HWND; Reason: LPCWSTR): Bool; stdcall; external user32;
function ShutdownBlockReasonDestroy(hWnd: HWND): Bool; stdcall; external user32;
procedure TForm1.WMEndSession(var Msg: TWMEndSession);
begin
inherited;
Msg.Result := lresult(False);
ShutdownBlockReasonCreate(Handle, 'please wait while muting...');
Sleep(45000); // do your work here
ShutdownBlockReasonDestroy(Handle);
end;
procedure TForm1.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
inherited;
Msg.Result := lresult(False);
ShutdownBlockReasonCreate(Handle, 'please wait while muting...');
Sleep(45000); // do your work here
ShutdownBlockReasonDestroy(Handle);
end;
Update
Changing the message result to true and removing the sleep changes nothing.
procedure TForm1.WMEndSession(var Msg: TWMEndSession);
begin
inherited;
Msg.Result := lresult(True);
ShutdownBlockReasonDestroy(Application.MainForm.Handle);
ShutdownBlockReasonCreate(Application.MainForm.Handle, 'please wait while muting...');
end;
procedure TForm1.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
inherited;
Msg.Result := lresult(True);
ShutdownBlockReasonDestroy(Application.MainForm.Handle);
ShutdownBlockReasonCreate(Application.MainForm.Handle, 'please wait while muting...');
end;
According to the documentation to block shutdown you need to return FALSE in response to WM_QUERYENDSESSION.
What's more, you must not do work in this message handler. The work must happen elsewhere. If you don't respond to this message in a timely fashion the system won't wait for you.
Call ShutdownBlockReasonCreate before you start working.
Whilst working return FALSE from WM_QUERYENDSESSION. Don't work whilst handling this message. Return immediately.
When the work is done call ShutdownBlockReasonDestroy.
The handler for WM_QUERYENDSESSION can look like this:
procedure TMainForm.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
if Working then
Msg.Result := 0
else
inherited;
end;
And then the code that performs the work needs to call ShutdownBlockReasonCreate before the work starts, ShutdownBlockReasonDestroy when the work ends, and make sure that the Working property used above evaluates to True during work.
If your work blocks the main thread then you are in trouble. The main thread must be responsive, otherwise the system won't wait for you. Putting the work in a thread is often the way forward. If your main window is not visible then you don't get a chance to block shutdown. The details are explained here: http://msdn.microsoft.com/en-us/library/ms700677.aspx
If you get as far as being sent WM_ENDSESSION then it's too late. The system is going down come what may.
To test it I simply send "end task" to it through the task manager.
That has nothing to do with shutdown blocking. The way you test shutdown blocking is to logoff. If the user insists on killing your process there is little that you can do about it. Sertac's answer covers this in detail.
Finally, ignoring the return values of API calls is also very poor form. Don't do that.
Your code seems to be completely ignored because you're not testing it. You're sending "end task" to it through the task manager, the code you posted is only effective when the system is shutting down.
What is different with Windows 8 seems to be how task manager behaves. Before Windows 8, an end task from task manager will first try closing the app gracefully (sends a WM_CLOSE), this you're handling with OnCloseQuery. But when the application denies closing, the task manager will offer ending the process forcefully. This, you cannot handle. Same if you choose "end process" from task manager.
The Windows 8 task manager does not offer an additional dialog for forcefully closing the application, but immediately proceeds doing so when the application denies closing.
Here are some solution for some different cases tested in Delphi 11.1 Alexandria:
The Form OnCloseQuery gets called when app is being closed by user or by system shutdown, to know which one of these two events is triggered, call GetSystemMetrics and pass SM_SHUTTINGDOWN as an argument.
System metric SM_SHUTTINGDOWN is set when there is a pending system shutdown, clear otherwise.
This is all you need if you only want say suppress exit confirmation message to the user if system is shutting down:
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if GetSystemMetrics (SM_SHUTTINGDOWN) > 0 then
Exit; // app is being closed by a system shutdown
// app being closed by user, ask user to confirm exit
if not ConfirmAppExit then
CanClose := False;
end;
Please note if the system is shutting down your Form OnCloseQuery will be called but your Form OnClose will not be called, any code you put in OnClose will not be executed. So, don't put code there if you want it to be executed on system shutdown, Instead, use the WM_EndSession handler described below.
If you want more than that and be able to block the shutdown, First write a handler for the message WM_QueryEndSession:
procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QueryEndSession;
Inside this handler do not do anything except returning message result, this message is sent to your app to check if it agrees to shutdown the system or not, it does not mean the system shutdown is taking place right now, because all apps must agree to this message first, but if just one app denies this message (return False) then shutdown will not happen (When shutdown is really
taking place you will receive WM_EndSession message).
In the WM_QueryEndSession handler check if there are any critical running tasks which must be completed in one go or if interrupted will cause data loss, If there is a critical task running return False to deny system shutdown, like:
procedure TForm1.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
if CriticalTaskRunning then
Msg.Result := 0 // Deny system shutdown
else
inherited; // Agree to system shutdown, Same as Msg.Result := 1
end;
Do not return False if your task is not critical and can be interrupted, and do not interrupt your task at this point, just return True and keep your task running because some other app may deny the shutdown and you just interrupted your task for nothing!, Interrupt your task only when you receive WM_EndSession message which means all applications agreed to the shutdown and the system is really shutting down.
By returning False, shutdown is now denied, using ShutdownBlockReasonCreat at this time is redundant, but you can use that to explain to the user (in shutdown screen) why your app is blocking shutdown. If you use this be sure to call ShutdownBlockReasonDestroy after your task is finished.
When receiving WM_EndSession you know now the system is really shutting down.
If you need to do cleanup, abort any running tasks, save changes close DB/files ...etc, then you can use ShutdownBlockReasonCreate to block shutdown until you finish cleaning up, then unblock shutdown once finished, like:
procedure WMEndSession (var Msg: TWMEndSession);
begin
if CleanUpRequired then
begin
ShutdownBlockReasonCreate (Handle, 'My app is preparing to close, just a sec...');
try
DoCleanUp;
finally
ShutdownBlockReasonDestroy (Handle);
end;
end;
end;
Some other shutdown block methods suggest you create a shutdown block every time you start a task and destroy the shutdown block once your task is finished even if the system is not shutting down! my approach here is only create a shutdown block when it is necessary and only when the system is really shutting down.
I Hope it's useful for someone!

Indy TCP Server freezing when deactivating

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;

Indy throws off multiple Exceptions when closing a socket. How to do a clean automatic reconnect?

I have a Delphi 6 application that uses the Indy 9 components to maintain a persistent IdTCPConnection with an external device. Some times I want to break the connection and then reconnect to the device. I have found when I do that Indy throws off as many as 3 separate Exceptions. This has made developing a clean reconnection strategy somewhat messy since the Indy component is on a background thread and I don't want to break the thread Execute() loop.
What is a good or clean way to absorb the 3 Exceptions before I attempt to reconnect? The 3 Exceptions I have seen are, in order of appearance, EIdClosedSocket, EIdConnClosedGracefully, EIdSocketError (with LastError = WSAECONNABORTED). I want to wait until all Exceptions related to the closing of the socket have propagated before attempting to reconnect, but I'm not sure how to structure my code loop to do that.
Only one exception will reach your thread's code at a time. Simply wrap the code inside your thread loop with a try/except block and then reconnect on the next loop iteration.
while not Terminated do
begin
if not Client.Connected then
try
Client.Connect;
except
Sleep(2500);
Continue;
end;
try
// communicate with device as needed...
except
on E: EIdException do
begin
// Indy socket error, reconnect
Client.Disconnect;
Client.InputBuffer.Clear;
end;
on E: Exception do
begin
// something else happened in your code...
end;
end;
end;

Resources