"Emergency" termination of omnithread IOmniParallelTask - delphi

Background
I have a unit test in which I check if my handler code code performs well during multi-thread stress:
procedure TestAppProgress.TestLoopedAppProgressRelease_SubThread;
begin
var bt:=Parallel.ParallelTask.NumTasks(1).NoWait.Execute(
procedure
begin
SetWin32ThreadName('TestLoopedAppProgressRelease_SubThread');
RunLoopedAppProgressRelease;
end
);
lSuccess:=bt.WaitFor(cRunLoopTimerMilliSecs*2);
if not lSuccess then
bt.Terminate; // emergency termination, unit test failed <<< How do I do this?
Check(lSuccess,'Failed to finish within expected time');
end;
in case the parallel thread fails to complete within the expected time, something is wrong and my check fails.
Unfortunately if the parallel task hangs, it never gets to an end and my unit test freezes because of the release of the bt interface at the end of my routine that waits for the never ending parallel task to complete.
So I need to shutdown my parallel task the evil way.
NOTE 1: It's a unit test, I don't really care about the thread cleanup: something needs to be fixed anyway if the unit test fails. I just don't want my entire unit test suite to hang/freeze, but simply report the failure and to continue with the next test in my suite.
NOTE 2: The Numthreads(1) could be omitted or an arbitrary number of threads.
Here's the Q:How can I terminate an IOmniParallel task forcibly?

Okay, I should have done better research. Here's the solution for a single background thread, just use the IOmniTaskControl interface:
procedure TestAppProgress.TestLoopedAppProgressRelease_SubThread;
begin
var lTask:=CreateTask(
procedure (const aTask:IOmniTask)
begin
RunLoopedAppProgressRelease;
end,
'TestLoopedAppProgressRelease_SubThread'
);
lTask.Run;
var lSuccess:=lTask.WaitFor(cRunLoopTimerMilliSecs*2);
if not lSuccess then
lTask.Terminate; // emergency termination, unit test failed
Check(lSuccess,'Failed to finish within expected time');
end;
And in case you want to run it in multiple subthreads, you need to wrap it up in an IOmniTaskGroup:
procedure TestAppProgress.TestLoopedAppProgressRelease_MultiSubThread;
const cThreads=4;
begin
var lTaskGroup:=CreateTaskGroup;
for var i:=1 to cThreads do
begin
var lTask:=CreateTask(
procedure (const aTask:IOmniTask)
begin
RunLoopedAppProgressRelease;
end,
'TestLoopedAppProgressRelease_SubThread '+i.ToString
);
lTaskGroup.Add(lTask);
end;
lTaskGroup.RunAll;
var lSuccess:=lTaskGroup.WaitForAll(cRunLoopTimerMilliSecs*2);
if not lSuccess then
lTaskGroup.TerminateAll; // emergency termination, unit test failed
Check(lSuccess,'Failed to finish within expected time');
end;
Using delays/breakpoints in my debugger I verfied the unit test (error) handling now works as expected. This is including expected memory leaks due to the killed threads.
I still feel IOmniParallelTask should have an Terminate method similar to the ones in IOmniTask and IOmniTaskGroup

Related

How to prevent application from freezing in a long running process?

The app seems to freeze sometimes when we try to pass a lot of imported files at once, which is done a each call of the function below for each file so the proposed solution is to add a sleep, but I can't seem to find proper documentation or explaining on how to handle it, or if I can even pass it as a parameter in a function.
This is the call for the proc
OpenQuery(FOrderToImportQuery.Database,FOrderToImportQuery);
My suggested idea if I can pass Sleep as Param
OpenQuery(FOrderToImportQuery.Database,FOrderToImportQuery, Sleep(200));
This is the function itself minus the sleep
procedure OpenQuery(aDatabase : TIBDatabase; aQuery : TIBQuery);
begin
if aDatabase.Connected = false then
databaseConnect(aDatabase);
if aDatabase.connected then
begin
try
aQuery.Open;
except
//try
aDatabase.ForceClose;
aDatabase.Open;
aQuery.Open;
{
except
on e: exception do
begin
Log('Error opening query : '+e.Message);
end;
end;
}
end;
end;
end;
The idea is I want the call to wait so it can complete properly before being called again. Would it be just fine to put Sleep at the end of the function itself?(Before the last END)
Or would passing it as a parameter in the call of the function be best? And if is so, how is this achieved... I can't find any doc on this particular circumstance.
The idea is I want the call to wait so it can complete properly before being called again.
Then the idea of using Sleep() is completely misconceived.
If, in a single thread, you call procedures A, B and C, as in
A;
B;
C;
then execution in the thread will only ever proceed to B after the call to A returns. Adding a Sleep() in either of them or in between them will only delay things: if there is a "log-jam" in A, adding a call to Sleep() in or after it will make no difference whatsoever. The fact that A, B and C all call your OpenQuery makes no difference either.
This is true even if A runs an asynchronous query, because the whole point of a call to an asynchronous query is that the call returns before the query completes - an asynchronous query spawns its own background thread in which the query actually executes, then typically passes the results back to the VCL thread via a call to Synchronize().
You have had comments suggesting that you put your query in a separate worker thread (separate from the VCL thread, that is). That's fine for stopping the VCL thread seizing up while waiting for the query(s) to complete, but including calls to Sleep() in the worker thread won't help there either.
So, the real answer to your q is for you to investigate and solve why a single call to OpenQuery causes the program to hang. But that's not what you've asked ...
First of all, let me say that I'm assuming your code is as optimized as it can be, and the time it takes to complete is inherently long. If you believe this might not be the case, you should open a new question with the details of your queries so we can help you on this.
Sleeping your main thread is definitely not the answer
The Sleep function will actually suspend the main thread for the amount of milliseconds specified. So, you will actually just be freezing your gui even more than now.
Worker thread
Creating a worker thread to handle the long-running work is probably your best bet to keep your program responsive while it's doing all the dirty work.
You'll have to take some precautions, though, because you probably don't want the user to be using the program while it's running the worker thread. For example, you don't want the user to click the start button again; or close the application, etc. But if these precautions are something like freezing the main thread, then you better just freeze it with the long-running work, anyway.
Maybe you will want a cancel button somewhere, if this is a process that can be interrupted in the middle (proper control of database transactions could provide this option safely).
Your worker thread could be something along these lines:
type
TWorkerThread = class(TThread)
private
{ Private declarations }
FDatabase: TIBDatabase;
FListQueries: TStringList;
protected
procedure Execute; override;
public
constructor Create(aDatabase: TIBDatabase; ListQueries: TStringList; CreateSuspended: Boolean);
destructor Destroy; override;
end;
implementation
{ TWorkerThread }
constructor TWorkerThread.Create(aDatabase: TIBDatabase; ListQueries: TStringList; CreateSuspended: Boolean);
begin
FListQueries.Create;
FListQueries.Assign(ListQueries);
FDatabase := aDatabase;
inherited Create(CreateSuspended);
end;
destructor TWorkerThread.Destroy;
begin
FListQueries.Free;
inherited;
end;
procedure TWorkerThread.Execute;
var i: Integer;
ibQuery: TIBQuery;
begin
{ Place thread code here }
ibQuery := TIBQuery.Create(aDatabase);
try
for i := 0 to FListQueries.Count - 1 do begin
if Terminated then
Exit;
ibQuery.SQL.Clear;
ibQuery.SQL.Add(FListQueries[i]);
OpenQuery(FDatabase, ibQuery);
end;
finally
ibQuery.Free;
end;
end;
PS: I'm sorry if there are compilation errors or if code for TIBDatabase/TIBQuery is wrong, I don't use any of these.
PPS: There is probably a problem with this code, though: I believe that the TIBConnection is very likely to not be thread-safe (I believe the client library itself is not). So you actually should create one connection just for use within the worker thread, rather than just use the same from main thread. I'll leave this correction for you, though. ;)

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!

Run without TOmniEventMonitor

How can I run tasks without TOmniEventMonitor? If I start them without it the main thread freezes. Which makes no sense because OmniThreadLibrary is supposed to be based on TThread. UnObserved doesn't really fix this because it just makes an internal copy of the same thing.
type
TWorker = class(TOmniWorker)
function Initialize: Boolean; override;
constructor Create;
end;
begin
var
Task: IOmniTaskControl;
begin
Task := CreateTask(TWorker.Create()).Run; // blocks main thread
Task := CreateTask(TWorker.Create()).UnObserved.Run; // will create internal monitor each time
Task := CreateTask(TWorker.Create()).OnTerminated().Run; // will create internal monitor each time
end.
If I create a TThread it doesn't need any kind of "Monitors" and it doesn't block the main thread either. I am not sending any kind of messages so why the need for a "Monitor"?
You are doing exactly that thing mentioned chapter 4.4 in the book Parallel Programming with OmniThreadLibrary as
The simplest example of the wrong approach can be written in one line:
CreateTask(MyWorker).Run;
As a solution you can assign the result of CreateTask to a variable with a scope that covers the runtime of the process.
The other solution (as you found yourself) is to use a monitor.

Preventing crash when doing time consuming task with COM (SKYPE4COM)

I am using the Skype4COM control. My program is trying to delete around 3K contacts from my contact list in Skype using a For loop, however
1) It takes a lot of time
2) it may crash, with a "MyApp has stopped working"
My guess is that somehow I need to "slow down" what I am doing.
Would I do that with Sleep();? Because I am not sure if that is also gonna "pause" the connection between Skype and my program.
To summarize: I am doing an action with a huge ammount of entries, and because of that big ammount, my program is hanging for a long time, and eventually crashes (sometimes). Is there a way to prevent that?
Skype4COM is STA by the way.
Thanks!
Move the processing into a separate thread. Your problem appears to be that Windows thinks the app has stopped responding because it's not processing it's message loop.
Calling Application.ProcessMessages is the wrong solution, because it does a lot more than you might think. You can end up with problems with reentrancy, or things happening that you don't expect.
Make sure that the thread calls CoInitialize before it creates the COM object, and calls CoUnitialize when it's done. You can find examples of using COM in a thread here; the article refers to ADO, but demonstrates the use of CoInitialize/CoUninitialize.
EDIT: After the comments, I'm adding an example of receiving a custom message in a Delphi app. The thread will need access to the UM_IDDELETED constant; you can do this by (preferably) adding it to a separate unit and using that unit in both your main form's unit and the thread's unit, or simply by defining it in both units.
// uCustomMsg.pas
const
UM_IDDELETED = WM_APP + 100;
// Form's unit
interface
uses ..., uCustomMsg;
type
TForm1=class(TForm)
// ...
private
procedure UMIDDeleted(var Msg: TMessage); message UM_IDDELETED;
//...
end;
implementation
procedure TForm1.UMIDDeleted(var Msg: TMessage);
var
DeletedID: Integer;
begin
DeletedID := Msg.WParam;
// Remove this item from the tree
end;
// Thread unit
implementation
uses
uCustomMsg;
// IDListIdx is an integer index into the list or array
// of IDs you're deleting.
//
// TheFormHandle is the main form's handle you passed in
// to the thread's constructor, along with the IDList
// array or list.
procedure TYourThread.Execute;
var
IDToDelete: Integer; // Your ID to delete
begin
while not Terminated and (IDListIdx < IdList.Count) do
begin
IDToDelete := IDList[IDListIdx];
// ... Do whatever to delete ID
PostMessage(TheFormHandle, UM_IDDELETED, IDToDelete, 0);
end;
end;
if you are using a loop to delete each contact you can place a call to Application.ProcessMessages this should fix the issue
[edit]
the call should be in the loop

Deadlock when closing thread

I created a class that opens a COM port and handles overlapped read and write operations. It contains two independent threads - one that reads and one that writes data. Both of them call OnXXX procedures (eg OnRead or OnWrite) notifying about finished read or write operation.
The following is a short example of the idea how the threads work:
TOnWrite = procedure (Text: string);
TWritingThread = class(TThread)
strict private
FOnWrite: TOnWrite;
FWriteQueue: array of string;
FSerialPort: TAsyncSerialPort;
protected
procedure Execute; override;
public
procedure Enqueue(Text: string);
{...}
end;
TAsyncSerialPort = class
private
FCommPort: THandle;
FWritingThread: TWritingThread;
FLock: TCriticalSection;
{...}
public
procedure Open();
procedure Write(Text: string);
procedure Close();
{...}
end;
var
AsyncSerialPort: TAsyncSerialPort;
implementation
{$R *.dfm}
procedure OnWrite(Text: string);
begin
{...}
if {...} then
AsyncSerialPort.Write('something');
{...}
end;
{ TAsyncSerialPort }
procedure TAsyncSerialPort.Close;
begin
FLock.Enter;
try
FWritingThread.Terminate;
if FWritingThread.Suspended then
FWritingThread.Resume;
FWritingThread.WaitFor;
FreeAndNil(FWritingThread);
CloseHandle(FCommPort);
FCommPort := 0;
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Open;
begin
FLock.Enter;
try
{open comm port}
{create writing thread}
finally
FLock.Leave;
end;
end;
procedure TAsyncSerialPort.Write(Text: string);
begin
FLock.Enter;
try
{add Text to the FWritingThread's queue}
FWritingThread.Enqueue(Text);
finally
FLock.Leave;
end;
end;
{ TWritingThread }
procedure TWritingThread.Execute;
begin
while not Terminated do
begin
{GetMessage() - wait for a message informing about a new value in the queue}
{pop a value from the queue}
{write the value}
{call OnWrite method}
end;
end;
When you look at the Close() procedure, you will see that it enters the critical section, terminates the writing thread and then waits for it to finish.
Because of the fact that the writing thread can enqueue a new value to be written when it calls the OnWrite method, it will try to enter the same critical section when calling the Write() procedure of the TAsyncSerialPort class.
And here we've got a deadlock. The thread that called the Close() method entered the critical section and then waits for the writing thread to be closed, while at the same time that thread waits for the critical section to be freed.
I've been thinking for quite a long time and I didn't manage to find a solution to that problem. The thing is that I would like to be sure that no reading/writing thread is alive when the Close() method is left, which means that I cannot just set the Terminated flag of those threads and leave.
How can I solve the problem? Maybe I should change my approach to handling serial port asynchronously?
Thanks for your advice in advance.
Mariusz.
--------- EDIT ----------
How about such a solution?
procedure TAsyncSerialPort.Close;
var
lThread: TThread;
begin
FLock.Enter;
try
lThread := FWritingThread;
if Assigned(lThread) then
begin
lThread.Terminate;
if lThread.Suspended then
lThread.Resume;
FWritingThread := nil;
end;
if FCommPort <> 0 then
begin
CloseHandle(FCommPort);
FCommPort := 0;
end;
finally
FLock.Leave;
end;
if Assigned(lThread) then
begin
lThread.WaitFor;
lThread.Free;
end;
end;
If my thinking is correct, this should eliminate the deadlock problem. Unfortunately, however, I close the comm port handle before the writing thread is closed. This means that when it calls any method that takes the comm port handle as one of its arguments (eg Write, Read, WaitCommEvent) an exception should be raised in that thread. Can I be sure that if I catch that exception in that thread it will not affect the work of the whole application? This question may sound stupid, but I think some exceptions may cause the OS to close the application that caused it, right? Do I have to worry about that in this case?
Yes, you should probably reconsider your approach. Asynchronous operations are available exactly to eliminate the need for threads. If you use threads, then use synchronous (blocking) calls. If you use asynchronous operations, then handle everything in one thread - not necessarily the main thread, but it doesn't make sense IMO to do the sending and receiving in different threads.
There are of course ways around your synchronization problem, but I'd rather change the design.
You can take the lock out of the Close. By the time it returns from the WaitFor, the thread body has noticed it has been terminated, completed the last loop, and ended.
If you don't feel happy doing this, then you could move setting the lock just before the FreeAndNil. This explicitly lets the thread shutdown mechanisms work before you apply the lock (so it won't have to compete with anything for the lock)
EDIT:
(1) If you also want to close the comms handle do it after the loop in the Execute, or in the thread's destructor.
(2) Sorry, but your edited solution is a terrible mess. Terminate and Waitfor will do everything you need, perfectly safely.
The main problem seems to be that you place the entire content of Close in a critical section. I'm almost sure (but you'll have to check the docs) that TThread.Terminate and TThread.WaitFor are safe to call from outside the section. By pulling that part outside the critical section you will solve the deadlock.

Resources