I am having a problem with some code that was written by a developer that has now left our company, the code implements a tcpserver that responds to an XML based protocol. This appears to be working absolutely fine in our test environment but one or two customers are having problems with application shutdown.
I have traced this to what appears to be a deadlock when tidtcpserver.active = false is called. I am already aware that a deadlock can be caused by one of the connection treads making a synchronised call to the main thread, whilst the main thread is waiting for the connection threads to terminate.
I am already using a tidthreadsafestringlist to pass the data to the main thread for processing, and where I need to call a procedure from the main thread I have created a tidnotify descendant to do this. can anyone think of anything else to look for.
I had already been checking the exception handling,
this is what i have in the onexecute event
try
// code to handle connection including tidnotify etc....
except
on E:Exception do
begin
if (e.InheritsFrom(EIdSilentException) = False) then
TXMLTraceNotify.XMLTrace('TCPServerExecute: ' + E.Message,ttProblem, FTraceProc);
raise; //we must raise all exceptions for indy to handle them.
end;
end;
Here is how i'm using the TS-stringlist
Declaration.
public
TransactionStrings: TIdThreadSafeStringList;
its created in the constructor and destroyed in the destructor.
this is how i'm adding to it in the context of the tcpserver.
TransactionStrings.Add(newTrans.AsString);
And this is how i'm reading from it in the context of the main application thread
slXMLTrans := TStringList.Create;
try
slTemp := FCustomXMLServer.TransactionStrings.Lock;
try
slXMLTrans.Assign(slTemp);
slTemp.Clear;
finally
FCustomXMLServer.TransactionStrings.Unlock;
end;
if slXMLTrans.Count > 0 then
begin
for i := 0 to Pred(slXMLTrans.Count) do
TAbstractTerminal.ProcessXMLTrans(slXMLTrans[i]);
slXMLTrans.Clear;
end;
finally
slXMLTrans.Free;
end;
I think this is the correct way to use it but I await your comments.
Deadlocks when setting the Active property to False are a classic, and often discussed, issue when using TIdTCPServer incorrectly. The only way the server deadlocks is when the connection threads do not terminate correctly. That MAY be caused by synchronizing operaton to the main thread while the main thread is busy deactivating the server, in which case the use of TIdNotify would does eliminate any such deadlock conditions.
However, that is not the only way to prevent a connection thread from terminating. Another possibility is if your server event handlers have exception handling in them that is blocking Indy's internal notifications from being processed. That can cause runaway threads that keep running but never know that they need to stop. If you are catching exceptions in your code, make sure you re-throw any EIdException-derived exceptions and let the server handle them internally.
Related
I need to develop a TCP server and client with persistent connections using Indy and Delphi XE2. Almost everything is going well.
This service is a critical service, so I need to put in some protection in the server to prevent unnecessary processing or freezes. Because of this, I create a thread to check a timeout for critical processes.
I made this TIdSync class:
type
TSync = class(TIdSync)
protected
procedure DoSynchronize; override;
end;
procedure TSync.DoSynchronize;
var
oTimeOut: TThreadTimeOut;
begin
...
oTimeOut := TThreadTimeOut.Create(AContext, WaitTimeOut*2, Self);
oTimeOut.Start;
...
// the code below is just a test, **this is the key to my question**
// if something goes wrong in any subroutine of DoSynchronize, I want
// to stop execution of this object and destroy it. In the thread above
// I want to test when the timeout elapses. If this IdSync object still
// exists and if this routine is still executing, I want to stop execution
// of any routine or subroutine of this object to avoid freezing the
// service and stop memory consumption and CPU usage
while true do begin
Sleep(100);
end;
//If everything is OK
oTimeOut.Stop;
end;
procedure TThreadTimeOut.execute;
var
IniTime: DWORD;
begin
IniTime := GetTickCount;
while GetTickCount < IniTime + TimeOut do begin
Sleep(SleepInterval);
if StopTimeOut then
Exit;
end;
if ((Terminated = False) or (StopTimeOut)) and (IoHandler <> nil) then begin
IOHandler.Connection.IOHandler.Close;
IdSync.Free; //here I try to make things stop execution but the loop to test is still running
end;
end;
This code above works fine to stop receiving and sending data when the timeout elapses, but not to stop execution of TIdSync. How can I do that?
There is no timeout logic in TIdSync (largely because there is no timeout logic in TThread.Synchronize(), which TIdSync uses internally).
You cannot destroy a TIdSync object while it is running. A synced procedure cannot be aborted prematurely once it has been queued for execution, or has started running. It must be allowed to run to completion.
TIdSync.DoSynchronize() (or any method synced with TThread.Queue() or TThread.Synchronize()) is executed in the context of the main UI thread. Long-running code should be executed in its own thread, not in the main UI thread. Make sure the main UI thread is not blocked from processing new messages and sync requests in a timely manner.
If you want to stop a synced procedure, you need to have it handle a TEvent object or other flag which worker threads can signal when needed, and that the procedure checks periodically so it can exit as soon as possible (either gracefully or by raising an exception).
Synched operations of any nature should be short, to prevent blockages/deadlocks, resource starvation, etc. You need to re-think your design. You are doing things the wrong way.
I needed to create statefull ISAPI extension for my project. I successfully made a TSession object that is contained in a TSessionList = class(TObject). For cleanup of expired sessions I made a cleanup thread (TThread descendant) that periodically scans TSessionList and frees all expired sessions.
I create the TSessionList and the CleanupThread in the dpr main execution block. Which is just fine. But actually I am not sure, where to put the destruction of CleanupThread. From documentation I found that ISAPI extension has to export TerminateExtension, which gets called just before the extension is unloaded. Default Delphi ISAPI extension of course exports such a function. So I've "overriden it" = exported my TerminateExtension that frees my session objects and then call default ISAPIAPP.TerminateExtensionProc.
Here it is how it looks like:
function TerminateExtension(dwFlags: DWORD): BOOL; stdcall;
begin
DoneSessions;
Result:= ISAPIApp.TerminateExtension(dwFlags);
end;
exports
GetExtensionVersion,
HttpExtensionProc,
TerminateExtension;
begin
CoInitFlags := COINIT_MULTITHREADED;
Application.Initialize;
InitSessions;
Application.CreateForm(TSOAPWebModule, SOAPWebModule);
Application.Run;
end.
The CleanupThread destruction is done in DoneSessions this way:
begin
CleanupThread.Free;
SessionList.Free;
end;
The CleanupThread is simple descendant of TThread, so do not look for anything specific in its destruction code.
The problem is that the TerminateExtension freezes just in CleanupThread.Free. Debugging further I did find that freeze happens in the TThread.WaitFor. I suspect there must be some kind of thread deadlock = ISAPI worker thread is waiting for my extension to terminate, which waits in TThread.WaitFor for the main thread to get signaled (or whatever).
I know I could overcome this situation calling CleanupThread.Terminate, then using direct WaitForSingleObject (or Multiple???) and finally freeing it. But that sounds a bit ... nonstandard.
Therefore my question is: How and when should i Free (Terminate - WaitFor - Destroy) any support threads in ISAPI extension to avoid thread deadlock?
BTW: I already found the same in standard DLL. If you put any thread.WaitFor in the dll unload proc, your main app freezes just in library unload. So the same question/answer hopefully applies here.
You should try signalling the thread terminate before calling free, for example;
CleanupThread.Terminate;
if CleanupThread.Waitfor(60000)<>WR_Abandoned then //wait for 60sec for cleanup
CleanupThread.free
else
//do something sensible on timeout or error
But without knowing what, exactly, the cleanup thread is trying to do it is hard to say what could be causing a deadlock. Often these are the result of race conditions, especially if a terminate & waitfor times out, so you need to specify what the thread is doing.
As a hack (and not good practice for multithreading) you can force a thread to exit using the winapi call TERMINATETHREAD (in the Windows unit);
TerminateThread(CleanupThread.handle,0);
As this forces an immediate exit on the thread but also means no thread cleanup is performed - remember that calling TTHREAD.TERMINATE does not guarantee that a thread will exit - this is entirely dependant on your thread code, if something is blocking your thread then it will not terminate in the normal manner. TerminateThread can solve this, at the expense of the code simply stopping where it is with no regard for any resource freeing or what other threads are up to.
I'm writing a custom thread which includes some added functionality. The part I'm confused about is how to handle the Execute procedure, while still expecting it to be descended into more inherited implementations.
My custom thread is overriding the Execute procedure and adding some of my own stuff, such as events OnStart, OnStop and OnException, as well as looping capabilities. I'm not sure how to design this in a way that expects it to be further used in a further inherited thread.
How do I make it possible to further inherit this custom thread while maintaining the Execute functionality?
Here's the execute procedure as I have overridden it...
procedure TJDThread.Execute;
begin
Startup;
try
while not Terminated do begin
if assigned(FOnExecute) then
FOnExecute(Self);
if not FRepeatExec then
Terminate
else
if FExecDelay > 0 then
Sleep(FExecDelay);
end;
finally
Cleanup;
end;
end;
I'm intending for FOnExecute to be actually an event of the thread, which is more-so a replacement of inheriting the Execute procedure - similar to how a service works. I don't think this is the right way to go... How do I make sure this is coded in a safe manner? I'm open to suggestions to another approach than an event - so long as it's aimed at the goal of making a custom TThread which can be inherited and further executed.
This custom thread I'm making includes some additional capabilities which don't come with the original TThread and yet will be extremely useful for many future projects. The additional capabilities are specifically OnStart and OnStop events (similar to how a service works), CoInitialize built in (and only used if told to, default = false), Repeated execution (default = false), and delay between executions (default = 0).
I agree with Rob. Don't use an event, use a virtual method. But even if you were to use the event and employ its "assignedness" to signal whether there is work to be done, you would need to protect the FOnExecute member as it can be set from different threads.
In one of our thread classes we use commands to do something similar:
procedure TCommandThread.SetCommand(const Value: ICommand);
begin
Lock;
try
Assert(not IsAvailable, 'Command should only be set AFTER the thread has been claimed for processing');
FCommand := Value;
if Assigned(FCommand) then
MyEvent.SetEvent;
finally
Unlock;
end;
end;
As SetCommand (the Command's setter) can be called from any ol' thread, setting the FCommand member is protected by the thread's critical section which is locked and unlocked through the Lock and Unlock methods.
Signalling MyEvent is done because our thread class uses a TEvent member to wait for work.
procedure TCommandThread.Execute;
begin
LogDebug1.SendFmtMsg('%s.Execute : Started', [ClassName]);
// keep running until we're terminated
while not Terminated do
try
// wait until we're terminated or cleared for take-off by the threadpool
if WaitForNewCommand then
if Assigned(FCommand)
and not Terminated then
// process the command if we're told to do so
CommandExecute;
except
LogGeneral.SendFmtError('%s.Execute : Exception occurred :', [ClassName]);
LogGeneral.SendException;
end;
LogDebug1.SendFmtMsg('%s.Execute : Finished', [ClassName]);
end;
WaitForNewCommand returns when the MyEvent is signalled. This is done when a command is assigned, but also when a (running) command is cancelled, when the thread is terminated etc. Note that Terminated is checked again just before CommandExecute is called. This is done because when WaitForNewCommand returns, we could be in a situation where both a command was assigned and terminate has been called. After all, signalling the event can be done twice from different threads and we don't know when or in what order anything happened.
CommandExecute is a virtual method that different thread classes can override. In the default implementation it provides for all the status processing around command execution so the commands themselves can concentrate on their own stuff.
procedure TCommandThread.CommandExecute;
var
ExceptionMessage: string;
begin
Assert(Assigned(FCommand), 'A nil command was passed to a command handler thread.');
Assert(Status = chsIdle, 'Attempted to execute non-idle command handler thread');
// check if the thread is ready for processing
if IsAvailable then // if the thread is available, there is nothing to do...
Exit;
try
FStatus := chsInitializing;
InitializeCommand;
FStatus := chsProcessing;
try
ExceptionMessage := '';
CallCommandExecute;
except
on E: Exception do begin
ExceptionMessage := E.Message;
LogGeneral.SendFmtError('%s.CommandExecute: Exception occurred during commandhandler thread execution:', [ClassName]);
LogGeneral.SendException;
end;
end;
finally
FStatus := chsFinalizing;
FinalizeCommand;
FStatus := chsIdle;
FCommand := nil;
// Notify threadpool we're done, so it can terminate this thread if necessary :
DoThreadFinished;
// Counterpart to ClaimThreadForProcessing which is checked in IsAvailable.
ReleaseThreadForProcessing;
end;
end;
CallCommandExecute is where, through several levels of indirection the FCommand's Execute method is called and where the real work of the command is done. That is why that call is directly protected with a try-except block. Other than that each Command in and of itself is responsible for thread safety with regard to the resources it uses.
ClaimThreadForProcessing and ReleaseThreadForProcessing are used to claim and release a thread. For speed's sake they don't use the thread's lock, but use the interlocked mechanism to change the value of the class' FIsAvailable member which is declared as a pointer and used as a boolean:
TCommandThread = class(TThread)
// ...
FIsAvailable: Pointer;
function TCommandThread.ClaimThreadForProcessing: Boolean;
begin
Result := Boolean(CompatibleInterlockedCompareExchange(FIsAvailable, Pointer(False), Pointer(True)));
// (See InterlockedExchange help.)
end;
function TCommandThread.ReleaseThreadForProcessing: Boolean;
begin
FIsAvailable := Pointer(True);
Result := IsAvailable;
end;
If any of the "finally" processing in the CommandExecute method needs to be done regardless of exceptions raised by other calls in that process, you will have to use nested try-finally's to ensure that is the case. The above method was simplified from our real code and the actual finally block is a set of nested try finally's to ensure that DoThreadFinished etc. get called regardless of exceptions in FinalizeCommand (and other calls in between).
Don't worry about how to make it safe to override Execute. Consumers who override your thread's Execute method won't work correctly (because they'll put their own operations around your bookkeeping code instead of within it). Provide a new virtual method for descendants to call instead. You could call it Run, for example, using Indy's TIdThread as a guide. It does much of the same things you're planning on.
Don't call Sleep(FExecDelay) - it's a kernel call that the descendant may not wish to make, so:
if (FExecDelay<>0) then Sleep(FExecDelay);
This gives a user the choice of avoiding the kernel call entirely.
I have issues with TThread.Synchronize - I would not want to force any user to have to call it.
TBH, I'm more used to putting code into an object class that is not descended from TThread, ie. a 'Ttask' that has a 'work' method that is called from the TThread. Having a separate class for the work is hugely more flexible and safer than adding data members and methods to a TThread descendant - it's easily queued in, queued out, PostMessaged etc. That, and not having access to the TThread instance stops developers using TThread.Synchronize, TThread.WaitFor and TThread.OnTerminate, so increasing the reliability and performance of the app.
I'm using several critical sections in my application. The critical sections prevent large data blobs from being modified and accessed simultaneously by different threads.
AFAIK it's all working correctly except sometimes the application hangs when exiting. I'm wondering if this is related to my use of critical sections.
Is there a correct way to free TCriticalSection objects in a destructor?
Thanks for all the answers. I'm looking over my code again with this new information in mind. Cheers!
As Rob says, the only requirement is that you ensure that the critical section is not currently owned by any thread. Not even the thread about to destroy it. So there is no pattern to follow for correctly destroying a TCriticalSection, as such. Only a required behaviour that your application must take steps to ensure occurs.
If your application is locking then I doubt it is the free'ing of any critical section that is responsible. As MSDN says (in the link that Rob posted), the DeleteCriticalSection() (which is ultimately what free'ing a TCriticalSection calls) does not block any threads.
If you were free'ing a critical section that other threads were still trying to access you would get access violations and other unexpected behaviours, not deadlocks, as this little code sample should help you demonstrate:
implementation
uses
syncobjs;
type
tworker = class(tthread)
protected
procedure Execute; override;
end;
var
cs: TCriticalSection;
worker: Tworker;
procedure TForm2.FormCreate(Sender: TObject);
begin
cs := TCriticalSection.Create;
worker := tworker.Create(true);
worker.FreeOnTerminate := TRUE;
worker.Start;
sleep(5000);
cs.Enter;
showmessage('will AV before you see this');
end;
{ tworker }
procedure tworker.Execute;
begin
inherited;
cs.Free;
end;
Add to the implementation unit of a form, correcting the "TForm2" reference for the FormCreate() event handler as required.
In FormCreate() this creates a critical section then launches a thread whose sole purpose is to free that section. We introduce a Sleep() delay to give the thread time to initialise and execute, then we try to enter the critical section ourselves.
We can't of course because it has been free'd. But our code doesn't hang - it is not deadlocked trying to access a resource that is owned by something else, it simply blows up because, well, we're trying to access a resource that no longer exists.
You could be even more sure of creating an AV in this scenario by NIL'ing the critical section reference when it is free'd.
Now, try changing the FormCreate() code to this:
cs := TCriticalSection.Create;
worker := tworker.Create(true);
worker.FreeOnTerminate := TRUE;
cs.Enter;
worker.Start;
sleep(5000);
cs.Leave;
showmessage('appearances can be deceptive');
This changes things... now the main thread will take ownership of the critical section - the worker thread will now free the critical section while it is still owned by the main thread.
In this case however, the call to cs.Leave does not necessarily cause an access violation. All that occurs in this scenario (afaict) is that the owning thread is allowed to "leave" the section as it would expect to (it doesn't of course, because the section has gone, but it seems to the thread that it has left the section it previously entered) ...
... in more complex scenarios an access violation or other error is possibly likely, as the memory previously used for the critical section object may be re-allocated to some other object by the time you call it's Leave() method, resulting in some call to some other unknown object or access to invalid memory etc.
Again, changing the worker.Execute() so that it NIL's the critical section ref after free'ing it would ensure an access violation on the attempt to call cs.Leave(), since Leave() calls Release() and Release() is virtual - calling a virtual method with a NIL reference is guaranteed to AV (ditto for Enter() which calls the virtual Acquire() method).
In any event:
Worst case: an exception or weird behaviour
"Best" case: the owning thread appears to believe it has "left" the section as normal.
In neither case is a deadlock or a hang going to occur simply as the result of when a critical section is free'd in one thread in relation to when other threads then try to enter or leave that critical section.
All of which is a round-a-bout way of saying that it sounds like you have a more fundamental race condition in your threading code not directly related to the free'ing of your critical sections.
In any event, I hope my little bit of investigative work might set you down the right path.
Just make sure nothing still owns the critical section. Otherwise, MSDN explains, "the state of the threads waiting for ownership of the deleted critical section is undefined." Other than that, call Free on it like you do with all other objects.
AFAIK it's all working correctly except sometimes the application hangs when exiting. I'm wondering if this is related to my use of critical sections.
Yes it is. But the problem is likely not in the destruction. You probably have a deadlock.
Deadlocks are when two threads wait on two exclusive resources, each wanting both of them and each owning only one:
//Thread1:
FooLock.Enter;
BarLock.Enter;
//Thread2:
BarLock.Enter;
FooLock.Enter;
The way to fight these is to order your locks. If some thread wants two of them, it has to enter them only in specific order:
//Thread1:
FooLock.Enter;
BarLock.Enter;
//Thread2:
FooLock.Enter;
BarLock.Enter;
This way deadlock will not occur.
Many things can trigger deadlock, not only TWO critical sections. For instance, you might have used SendMessage (synchronous message dispatch) or Delphi's Synchronize AND one critical section:
//Thread1:
OnPaint:
FooLock.Enter;
FooLock.Leave;
//Thread2:
FooLock.Enter;
Synchronize(SomeProc);
FooLock.Leave;
Synchronize and SendMessage send messages to Thread1. To dispatch those messages, Thread1 needs to finish whatever work it's doing. For instance, OnPaint handler.
But to finish painting, it needs FooLock, which is taken by Thread2 which waits for Thread1 to finish painting. Deadlock.
The way to solve this is either to never use Synchronize and SendMessage (the best way), or at least to use them outside of any locks.
Is there a correct way to free TCriticalSection objects in a destructor?
It does not matter where you are freeing TCriticalSection, in a destructor or not.
But before freeing TCriticalSection, you must ensure that all the threads that could have used it, are stopped or are in a state where they cannot possibly try to enter this section anymore.
For example, if your thread enters this section while dispatching a network message, you have to ensure network is disconnected and all the pending messages are processed.
Failing to do that will in most cases trigger access violations, sometimes nothing (if you're lucky), and rarely deadlocks.
There are no magical in using TCriticalSection as well as in critical sections themselves. Try to replace TCriticalSection objects with plain API calls:
uses
Windows, ...
var
CS: TRTLCriticalSection;
...
EnterCriticalSection(CS);
....
here goes your code that you have to protect from access by multiple threads simultaneously
...
LeaveCriticalSection(FCS);
...
initialization
InitializeCriticalSection(CS);
finalization
DeleteCriticalSection(CS);
Switching to API will not harm clarity of your code, but, perhaps, help to reveal hidden bugs.
You NEED to protect all critical sections using a try..finally block.
Use TRTLCriticalSection instead of a TCriticalSection class. It's cross-platform, and TCriticalSection is only an unnecessary wrapper around it.
If any exception occurs during the data process, then the critial section is not left, and another thread may block.
If you want fast response, you can also use TryEnterCriticalSection for some User Interface process or such.
Here are some good practice rules:
make your TRTLCriticalSection a property of a Class;
call InitializeCriticalSection in the class constructor, then DeleteCriticalSection in the class destructor;
use EnterCriticalSection()... try... do something... finally LeaveCriticalSection(); end;
Here is some code sample:
type
TDataClass = class
protected
fLock: TRTLCriticalSection;
public
constructor Create;
destructor Destroy; override;
procedure SomeDataProcess;
end;
constructor TDataClass.Create;
begin
inherited;
InitializeCriticalSection(fLock);
end;
destructor TDataClass.Destroy;
begin
DeleteCriticalSection(fLock);
inherited;
end;
procedure TDataClass.SomeDataProcess;
begin
EnterCriticalSection(fLock);
try
// some data process
finally
LeaveCriticalSection(fLock);
end;
end;
If the only explicit synchronisation code in your app is through critical sections then it shouldn't be too difficult to track this down.
You indicate that you have only seen the deadlock on termination. Of course this doesn't mean that it cannot happen during normal operation of your app, but my guess (and we have to guess without more information) is that it is an important clue.
I would hypothesise that the error may be related to the way in which threads are forcibly terminated. A deadlock such as you describe would happen if a thread terminated whilst still holding the lock, but then another thread attempted to acquire the lock before it had a chance to terminate.
A very simple thing to do which may fix the problem immediately is to ensure, as others have correctly said, that all uses of the lock are protected by Try/Finally. This really is a critical point to make.
There are two main patterns for resource lifetime management in Delphi, as follows:
lock.Acquire;
Try
DoSomething();
Finally
lock.Release;
End;
The other main pattern is pairing acquisition/release in Create/Destroy, but that is far less common in the case of locks.
Assuming that your usage pattern for the locks is as I suspect (i.e. acquireand release inside the same method), can you confirm that all uses are protected by Try/Finally?
If your application only hangs/ deadlocks on exit please check the onterminate event for all threads. If the main thread signals for the other threads to terminate and then waits for them before freeing them. It is important not to make any synchronised calls in the on terminate event. This can cause a dead lock as the main thread waits for the worker thread to terminate. But the synchronise call is waiting on the main thread.
Don't delete critical sections at object's destructor. Sometimes will cause your application to crash.
Use a seperate method which deletes the critical section.
procedure someobject.deleteCritical();
begin
DeleteCriticalSection(criticalSection);
end;
destructor someobject.destroy();
begin
// Do your release tasks here
end;
1) You call delete critical section
2) After you release(free) the object
I'm using Delphi 2006 and have a bit of a problem with an application I'm developing.
I have a form that creates a thread which calls a function that performs a lengthy operation, lets call it LengthyProcess. Inside the LengthyProcess function we also call several Dll functions which also create threads of their own.
The problem that I am having is that if I don't use the Synchronize function of my thread to call LengthyProcess the thread stops responding (the main thread is still responding fine). I don't want to use Synchronize because that means the main thread is waiting for LengthyProcess to finish and therefore defeats the purpose of creating a separate thread.
I have tracked the problem down to a function inside the dll that creates a thread and then calls WaitFor, this is all done using TThread by the way. WaitFor checks to see if the CurrentThreadID is equal to the MainThreadID and if it is then it will call CheckSychronization, and all is fine. So if we use Synchronize then the CurrentThreadID will equal the MainThreadID however if we do not use Synchronize then of course CurrentThreadID <> MainThreadID, and when this happens WaitFor tells the current thread (the thread I created) to wait for the thread created by the DLL and so CheckSynchronization never gets called and my thread ends up waiting forever for the thread created in the dll.
I hope this makes sense, sorry I don't know any better way to explain it. Has anyone else had this issue and knows how to solve it please?
If your secondary thread "stops responding," then I assume it has a message pump. (Otherwise, you need to explain what it stops responding to.) You appear to also wish for the thread to be able to detect when the tertiary thread finishes running. (The "primary" thread here is the VCL thread, which isn't involved at all.)
You tried using WaitFor, but were disappointed when you discovered that it blocks. That's what it has always been designed to do, though. Its behavior in the main thread is where it gets weird, so it's safe to call from the VCL thread even though it was never really meant to be used that way originally.
To process messages and wait for threads to finish running, you need to use one or more of the wait functions from the Windows API. Start with MsgWaitForMultipleObjects. It can wait for various types of kernel handles, including thread handles, but also notify you when messages are available. The idea is that you'll call that function in a loop. When it says messages are available, handle them, and then loop again to continue waiting.
The following is just an outline. You'll want to check the documentation for all the API functions used, and combine that with the rest of the knowledge you have about your own threads.
procedure TSecondaryThread.Execute;
var
ret: DWord;
ThreadHandle: THandle;
Msg: TMsg;
begin
ThreadHandle := TertiaryThread.Handle;
repeat
ret := MsgWaitForMultipleObjects(1, ThreadHandle, False, Infinite, qs_AllEvents);
case ret of
Wait_Object_0: begin
// The thread terminated. Do something about it.
CloseHandle(ThreadHandle);
PostQuitMessage(0);
// Put *something* in the parameter so further calls to MWFMO
// will have a valid handle. May as well use a handle to something
// that will never become signaled so all we'll get are more
// messages. I'm pretty sure you can't pass an empty array of
// handles; there must be at least one, and it must be valid.
ThreadHandle := Self.Handle;
end;
Wait_Object_0 + 1: begin
// At least one message is available. Handle *all* of
// them before calling MsgWaitForMultipleObjects again
while PeekMessage(Msg, 0, 0, 0, pm_Remove) do
case Msg.Message of
wm_Quit: begin
// Do something about terminating the tertiary thread.
// Then stop the message loop and the waiting loop.
Exit;
end;
else begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
end;
Wait_Timeout: Assert(False, 'Infinity has passed');
Wait_Failed: RaiseLastOSError;
else Assert(False, 'Unexpected return value');
end;
until False;
end;
The part about handling all the messages is important. As soon as you call GetMessage, PeekMessage, or WaitMessage, the OS marks all messages in the queue as "old," but MsgWaitForMultipleObjects will only return when there is a "new" message on the queue — one that arrived after the last call to PeekMessage.
HI, Thanks for your reply, yes i realize that my question isn't very clear and somewhat confusing; so i'll try to clarify things a bit, here goes..
All of the threads described below are derived from TThread.
I have a form which starts a thread but does not wait for it. The thread started by the form calls a function that performs a long task.
The function calls another function in a DLL, the function in the DLL starts a thread and waits for it. The thread started by the DLL function calls another function via synchronize.
Form->Starts a thread but does not wait->The thread calls a functions->The function calls a DLL function->The Dll function starts a thread and waits->The thread started by the DLL function calls another function via synchronize i.e Synchronize(UpdateRecords).
The problem is that the call to synchronize never returns because, from what i can see, it has entered some sort of dead lock.
How synchronize works: Synchronize puts the method call into a queue and sets an event, Synchronize then waits for the event to become signaled. When the main thread is idle it will process the method calls that are waiting in the queue, after it has processed a method call it will signal the associated event so that the thread that initiated the synchronization can continue on.
The thread that was started by the form does not use synchronize to call the function that performs the long task, if it does use synchronize then the application does not dead lock, but this defeats the purpose of use a thread for the long process.
I've tracked down the problem, it seems to be that the TApplication object created by the dll is not processing messages and has a handle of 0, how this happened I don't know (I didn't write the DLL, it was written by someone else), but it is a cause of the problem because it will never process the method called queued by synchronize.
I mentioned earlier that if i call the function that performs the long process from my thread using synchronize then the application does not dead lock. This is because the main thread will be responsible for calling the function that performs the long process. So the long process function calls a DLL function which starts another thread and then calls WaitFor. WaitFor checks to see if the current thread is the main thread, and if it is, it processes method calls which have been queued by synchronize, continuously in a loop until the thread the thread that it is waiting for is released (i.e. the method it queued via synchronize gets called and the wait event is signaled).
In WaitFor, if the current thread is not the main thread then WaitFor simply blocks until the thread it is waiting for is released.
Anyway i can't do anything about the application object in the dll because that dll is quite complex and used by a larger system. I guess i can expose a method in the dll which can process the methods in the synchronization queue, i can then call this method from my application while its idle.
Anyway again thanks for your help but i've solved this problem now.
Using the TThread class or even the Application object in a Delphi DLL is extremely unsafe. The RTL and VCL core classes, globals and singleton objects are designed to exist once per process and can't handle the namespace duplication and incomplete initialization in a stadard DLL library.
You may get around it by building with runtime packages (RTL & VCL are enough; you may also build your own with just the system units you reallt need), in the EXE and all DLLs referencing the runtime units (Forms and Classes, especially) - they get single shared namespace and the full EXE initialization sequence this way.
If you can't modify the DLL at all, you may try to set it's Application.Handle, MainThreadID, SyncEvent and WakeMainThread to the corresponding values in the main EXE module - this may work, but it's just as ugly as it looks like and it doesn't cover all edge-cases (the classes and important globals will still be duplicated).