[Webinar] Streamline your web hosting managementRegister Today

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 745
  • Last Modified:

Clean shutdown of multithreaded component

We have a COM component that internally uses several threads that interact with an external device. The component host (the front end app) creates a window to which the component threads send custom Windows messages (using SendMessage). The messages contain pointers to structures allocated inside the component.

When the host app starts to terminate, the WM_CLOSE message handler calls the component close function. This function sets some internal flags (to signal the internal threads to start terminating) and then waits for the threads to terminate by waiting on the thread handles. However, one or more of the threads may be blocked waiting in a SendMessage call - a message it has already placed on the app message queue, but which will never get serviced since the message queue thread is processing WM_CLOSE and now waiting for the threads to terminate. Classic deadlock problem.

I tried using SendMessageTimeout on the internal threads, and although this solves the shutdown problem it means that if the host brings up a modal dialog (i.e. stops processing the message queue during normal runtime operation) then messages from the component threads are lost.

Using PostMessage would require an allocated list for my data which not ideal.

How can I ensure clean thread shutdown in the component without ever losing any messages during runtime?

Thanks,

Jamie
0
JamieR
Asked:
JamieR
  • 11
  • 6
  • 3
  • +5
1 Solution
 
jkrCommented:
>>and then waits for the threads to terminate by waiting on
>>the thread handles

What about specifying a timeout in the wait operation and calling 'TerminateThread()' after it timed out?
0
 
JamieRAuthor Commented:
I'd like the threads to shutdown cleanly since each one contains it's own resources that need to be cleaned up.

0
 
jkrCommented:
Hmm, in this case, I'd have the threads periodically check a 'termination signal', e.g. an event:

if ( WAIT_TIMEOUT != WaitForSingleObject ( hTermEvent, 1))
{
  // ready to cleanup & terminate
}

Simply set this event to 'signaled' when calling your component's close function...
0
[Webinar] Improve your customer journey

A positive customer journey is important in attracting and retaining business. To improve this experience, you can use Google Maps APIs to increase checkout conversions, boost user engagement, and optimize order fulfillment. Learn how in this webinar presented by Dito.

 
basantCommented:
>However, one or more of the threads may be blocked waiting in a SendMessage call - a message it has already placed on the app message queue, but which will never get serviced since the message queue thread is processing WM_CLOSE and now waiting for the threads to terminate. Classic deadlock problem.

You should write the WM_CLOSE like this :

Case WM_CLOSE :
   ReplyMessage( ..);
// This will allow the caller to become successful. Now you can Post/Send messages/events to other threads to close and wait for them. This is the standard technique to avoid SendMessage Deadlock.

Look for the help of ReplyMessage.
0
 
JamieRAuthor Commented:
I did look at ReplyMessage, but I didn't try it. Looking at MSDN, it implies (?) that ReplyMessage allows the thread that called SendMessage to continue. But the message handler thread will be processing WM_CLOSE (where ReplyMessage is called), so surely this is not the messsage that I want to unblock?

Does ReplyMessage unblock all threads waiting on the current message queue or just the message currently being processed?


jkr: SendMessage may already have been called internally by the time WM_CLOSE is picked off the queue by the message handler...

Jamie
0
 
mikeblasCommented:
Never, ever, ever call TerminateThread().

Never.

..B ekiM
0
 
basantCommented:
>But the message handler thread will be processing WM_CLOSE (where ReplyMessage is called), so surely this is not the messsage that I want to unblock?

OK Try this :
When your main thread windows is closed.
The thread who is waiting for Sedmessage will fail ? Right but becuase you application's Main Thread is destroyed so Other Threads May not get a chance to check as they may not execute and program will execute. So my suggestion now is :


BOOL g_bClosed = FALSE;

WM_CLOSE :
....
  // Set a flag to show that
  // window is closed
  g_bClosed = TRUE;



....

Now you Override the
OnExitInstance if you are using a MFC or
After you handle the message


WinMain()
{
while( GetMessage( ... )

{
}
// Now windows has been closed and
Flag will be set to TRUE.
So Now Wait for other threads to close down. Send them the proper messages.
WaitforSingleObject( hThread, ...);


Other Threads :
...
...
SendMessage( hWnd, ... )
// This will fail if hWnd is closed
if( g_bClosed )
{
// Do Cleaning and Exit normally from Thread
}
0
 
NickRepinCommented:
If you don't need a return value from the host, it's much more easier to use PostMessage:

struct A* a=new A;
a->mem1=...

PostMessage(thread, a)

Inside the message handler, free 'a'. No lists are necessary.
0
 
jkrCommented:
Hmm, when using a MS compiler, this will lead to an assertion, as each module has its own heap...
0
 
NickRepinCommented:
Doesn't matter. Use GlobalAlloc instead of new.
0
 
MadshiCommented:
Well, how about this suggestion:

Don't wait for the threads in the WM_CLOSE handler. Let the WM_CLOSE handler simply destroy your main window. Then leave your message loop. But NOW - after your main window is destroyed and your message loop is quit, NOW wait for the threads, because now the threads' SendMessage calls should be aborted due to the destroyed main window (or am I wrong with that?).

Regards, Madshi.
0
 
JamieRAuthor Commented:
Nick,

A good solution, but I need to synchronize my internal threads with the host app, so PostMessage isn't ideal.

Jamie
0
 
PIGCommented:
I can't understud way You do not use events for synchronize netween all Your thread. It is simple and more clear. Then You have not problem with message queue. WaitForMultipleObjects allow make synchronization on many events.
0
 
JamieRAuthor Commented:
Sorry, but I don't think using events will really make any difference - although I agree it would probably make things simpler. I avoid Windows messaging as much as possible but in this case my threads are in a COM component that will drive many different apps written in many different languages. Sending messages to the app window seems like the best technique to use.

Anyway, if I used events the client app would have to create a thread to retrieve the messages by waiting on an event. If the app is shutting down, this thread may well be sending a windows message to the message queue (to update a control, whatever) so IT will now be deadlocked since the message handler thread is shutting the component. The component cannot close until the second app thread completes since it receives a pointer to component memory space.
0
 
JamieRAuthor Commented:
basant,

I did try some two stage shutdown stuff - start close in WM_CLOSE wait for close completion in WM_DESTROY, for example. This doesn't work with WM_ENDSESSION since the app doesn't receive either WM_CLOSE or WM_DESTORY. Also, I'm not sure if all types of Windows apps get two shutdown messages (console apps, VB apps, etc).
0
 
JamieRAuthor Commented:
Madshi,

Your idea is very similar to basant's but it's just a single stage shutdown after the message queue is destroyed. For some reason, this doesn't work. Not sure why, but I think it may be because SendMessage doesn't seem to always return straight away - or something...

Heard the expression "have yer cake and eat it?". I'm starting to think that's what I'm trying to do.
0
 
basantCommented:
Dear Madshi,  
 You didn't answer to my prev comments.
Why are you only trying to shutdown at Windows Messages. Why don't you try after Message Loop of WinMain.
??????????

I don't understand this. Since Window will be closed at this stage so All calls to Post/SendMessages will fail to that Wnd and that u can use as an exit criteria for different threads.
0
 
JamieRAuthor Commented:
basant,

I think you are referring to me (JamieR) since I asked the question.

Closing the component after the windows loop has terminated works superbly during normal shutdown, but when the app terminates through a WM_ENDSESSION call it still occasionally hangs. (I don't close the component in this handler since it would obviously fail). The main thread never returns from the message loop, probably because it doesn't receive WM_CLOSE and WM_DESTROY and all that stuff during a system shutdown.

Still, your solution is the best yet. I'll give you the points if nobody suggests anything better...

Thanks,

Jamie
0
 
basantCommented:
> WM_ENDSESSION

I think this message is send by windows at the time of Logoff/Shutdown. Am I right.


I think if it is feasible then
you can stop shutting down your Application by returning FALSE from
WM_ENDQUERYSESSION which will force user to shutdown your application manually.

Meanwhile I will think how to close the application normally be WM_ENDSESSION.
0
 
basantCommented:
How are you getting WM_ENDSESSION msg.
0
 
MadshiCommented:
Hmmm... yes, my suggestion is very similar to basant's. Somehow I missed his. I thought I was the first who suggestion that...   :-)

About WM_ENDSESSION: If you get this message and "wParam" is true, you can shutdown your main window, too.

Regards, Madshi.
0
 
JamieRAuthor Commented:
I think WM_QUERYENDSESSION and WM_ENDSESSION are the only shutdown message a window receives before a system close during a logoff, reboot, etc. It looks like the message handler call to GetMessage never returns zero (allowing it to exit the message loop) which is why the component doesn't shutdown properly...

Er, I've got a (slightly naff) solution.

Inside the component use SendMessageTimeout with a delay of say 1000 ms and loop while the call returns failure and the component is not shutting down. Effectively this means that the component threads will keep trying to send messages to the app window until it either responds or closes the component.

I hate polling, but I'm starting to think this is the only (other) way to break the possible deadlock loop short of posting messages. However, this should allow the component to be closed at any time by any thread, placing no restrictions on the app implementation.
Well, it might still need to call ReplyMessage if the message handler is going to block, but apart from that...

Jamie
0
 
basantCommented:
Dear JaimeR,
 my Suggestion will be ( If feasible )to
return FALSE from WM_QUERYENDSESSION
which will force the user to make your software shudown and then start logff/shutdown. Until unless the user don't shutdown ur s/w he will not be able to do log off/shutdown.

The other possibility :

I am unable to think of How one force his window to quit the message Loop when WM_QUERYENDSESSION is send to him.

I had some idea for that also.
May work for you.
What you do is to modify ur message loop :

while ( GetMessage(&msg,hWnd,0,0 )
{
    // Now before dispatching to WindowProc you handle it
   if( msg.message == WM_QUERY_ENDSESSION )
{
   DestroyWindow(hWnd); // This will destroy the Window
   break; // Come out of the loop
}
   Translate..
   DispatchMe...
}

// Do your deinitialization here.


I am not very sure whether it will work or not. But if ur threads are scheduled by the processor then This suggestion will work.
}
}
0
 
ktambasc98Commented:
I have a similar situation, I solved it with making the WM_CLOSE and the custom message handler threadsafe with critical sections.  

WM_CLOSE:
{
  EnterCriticalSection(&m_cm);
  CloseThread();
  //don't leave critical section,
  //because this window is closing.
}

WM_CUSTOM_MESSAGE_FROM_THREAD:
{
  if(TryEnterCriticalSection(&m_cm) ==    FALSE)
    return;
  else
  {
    //process thread
  }

The reason that the custom message handler only tries, is that I found during the WM_CLOSE, the thread could block, and of course that is your problem now.

You may also want to consider using COM events.  I am using reader threads that Fire events to my Window.  My model is a loosely coupled server, because I can Advise and Unadvise on those events, and then delete the COM server.  After I Unadvise, there is no way that my server can block my GUI, because no more events are present.

0
 
JamieRAuthor Commented:
ktambasc98,

Can you give me some more information on the IConnectionPoint solution? I had a quick look at MSDN, but I'm not familar with COM - and of course the documentation is in a totally different language (again)...

What is the sequence of (COM event) actions to send a message to the client and for client shutdown? Do I have to deal with thread marshalling, since more than one of my threads sends messages to the host app? Will the host app need to create it's own thread to retrieve the events?

Thanks,

Jamie
0
 
JamieRAuthor Commented:
Hello?
0
 
ktambasc98Commented:
the IConnectionPoint mechanism is two-fold.  First, you need to check the connection point box when you create your ATL/COM object in VC++.  Secondly, you need to provide a class derived from CCmdTarget to receive events.  You will want to look at AfxConnectionAdvise() and AfxConnectionUnadvise().  

In the call to AfxConnectionAdvise(), you provide the COM server pointer (I imported the typelib from my server, created a dispatch wrapper, use that pointer to Advise()) and you provide the CmdTarget class pointer.  since I wanted to receive events in a property page (and CPropertyPage inherits from CCmdTarget) i used this->GetIDispatch(FALSE) to pass the pointer of the sink object.  

The way the COM event works, is it keeps track of every client that has connected to it (the server), and when you fire a message, you will see that the Fire code is a for loop that fires the events.

You may have to deal with marshalling, because my server creates multiple threads, and each thread can fire an event.

Hope this helps,
Kevin
0
 
JamieRAuthor Commented:
It's probably a good answer but infortunately, having had a look, we don't know enough about COM to implment this solution....

Still, I guess you get the points. Thanks.
0

Featured Post

Receive 1:1 tech help

Solve your biggest tech problems alongside global tech experts with 1:1 help.

  • 11
  • 6
  • 3
  • +5
Tackle projects and never again get stuck behind a technical roadblock.
Join Now