Reentrancy problem ???

Reentrancy problem ???

Post by Senthilve » Fri, 02 Jul 2004 20:13:54


i,
I was just experimenting when i stumbled across the following problem.
I invoke a method call on my object that lives in another process and in an
STA.
Inside the method i spawn a thread and delegate the job to a new thread and
return.(I use this technique for performing asynchronous operations)

To pass the callback interface to the thread , i register it in the GIT and
pass the cookie to the newly spawned thread.
Since the method returns even before performing the requested job,client
application executes a wait so that the callback gets completed.
Here is my problem.Since the client application is waiting and has suspended
the thread I am not able to get my callback executed in the server and the
application hangs for ever.
Is this the place where i have to use IMessageFilter ??? Is this the
reentrancy problem Don Box dicusses in Essential COM ??
I have posted almost the full code of my sample server and the client here.

Server code (a little edited to make it short):

from idl file
====
interface ITestSTA : IUnknown
{
[helpstring("method CallMethod")] HRESULT CallMethod([in]ITestSTACb* pCb);
};

interface ITestSTACb : IUnknown
{
[helpstring("method CallMethod")] HRESULT Invoke([in,string]wchar_t*
pwszMsg);
};

coclass TestSTA
{
[default] interface ITestSTA;
[source,default] interface ITestSTACb;
};


Implementation of the object
===================

DWORD WINAPI Worker(LPVOID pArgs);
IGlobalInterfaceTable *gpGIT = 0;

STDMETHODIMP CTestSTA::CallMethod(ITestSTACb* pCb)
{

DWORD dwCookie = 0;
HRESULT hr = CoCreateInstance(CLSID_StdGlobalInterfaceTable,
NULL,
CLSCTX_INPROC_SERVER,
IID_IGlobalInterfaceTable,
(void **)&gpGIT);
hr = gpGIT->RegisterInterfaceInGlobal(pCb,IID_ITestSTACb,&dwCookie);


DWORD dwID = 0;
CreateThread(NULL,0,Worker,(void*)dwCookie,0,&dwID);
return S_OK;
}

DWORD WINAPI Worker(LPVOID pArgs)
{
CoInitialize(NULL);

ITestSTACb* pCB = 0;
DWORD dwCookie = reinterpret_cast<DWORD>(pArgs);
HRESULT hr =
gpGIT->GetInterfaceFromGlobal(dwCookie,IID_ITestSTACb,(void**)&pCB);

hr = pCB->Invoke(L"Hi Sen!!!");
hr = pCB->Release();

hr = gpGIT->RevokeInterfaceFromGlobal(dwCookie);
hr = gpGIT->Release();

CoUninitialize();
return 0;
}

The Client Code:
===========
#define ASSERT_HR assert(SUCCEEDED(hr));

class Sink:public ITestSTACb
{
public:
Sink(HANDLE &hEvent):m_hEvent(hEvent)
{ }
ULONG __stdcall AddRef()
{
return 1;
}
ULONG __stdcall Release()
{
return 2;
}

HRESULT __stdcall QueryInterface(REFIID riid,LPVOID* ppItf)
{
if( riid == IID_IUnknown || riid == IID_ITestSTACb )
{
reinterpret_cast<IUnknown*>(*ppItf =
static_cast<ITestSTACb*>(this))->AddRef();
return S_OK;
}
else
{
*ppItf = 0;
return E_NOINTERFACE;
}

}

HRESULT __stdcall Invoke(wchar_t* pwszMsg)
{
std::cout<< "Callback received!!!! Message is : "<<std::endl;
std::wcout<< pwszMsg<< std::endl;

SetEvent(m_hEvent);

return S_OK;
}
private:
HANDLE &m_hEvent;
};


int main(int argc, char* argv[])
{
HRESULT hr = S_OK;

hr = CoInitialize(NULL);

ASSERT_HR;

ITestSTA *pServer = 0;
hr =
CoCreateInstance(CLSID_TestSTA,NULL,CLSCTX_LOCAL_SERVER,IID_ITestSTA,(void**
)&pServer);

 
 
 

Reentrancy problem ???

Post by Kim Grma » Fri, 02 Jul 2004 20:36:04

Hi Senthilvel,

Looks like you're performing a cardinal sin; blocking in an STA:


Try replacing WaitForSingleObject with AtlWaitWithMessageLoop (or equivalent
code based on MsgWaitForMultipleObjects), which spins a message loop,
allowing your callbacks to come back into your apartment.

--
Best regards,
Kim Grman

 
 
 

Reentrancy problem ???

Post by Senthilve » Fri, 02 Jul 2004 20:53:09

Thanks for pointing out my "cardinal sin"
And i think you're acting as an eye opener for me too often !!!!
: ) : ) : )



equivalent
 
 
 

Reentrancy problem ???

Post by Igor Tande » Fri, 02 Jul 2004 23:18:45


Now, this here is a very, shall we say, curious and unusual design.
First the server bends over backwards to ensure the call is completed
asynchronously, then the client bends over backwards to obtain the
results of that call synchronously. What's the point of the exercise?
Why not just make the call synchronous and be done with it? Am I missing
something obvious?
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
 
 
 

Reentrancy problem ???

Post by Alexander » Sat, 03 Jul 2004 01:05:46

ell, your server probaly doesn't live in another process... :)
Otherwise your "cardinal sin" as Kim put it wouldn't affect your
example. Of course you shouldn't be blocking an STA, but you
could use the MTA and live happily ever after... No need of
GIT in that case either - just make sure both your threads
enter the MTA.

BTW, you have another bug - your waiting on the thread to
complete creates a race conditition. Instead of going to the pains
of creating an event and having the thread signal it (which is
very unreliable - what if the thread crashes while a structured
exception handler is in place and subsequently exits without
signaling your event?), you should wait on the thread handle
directly. Otherwise, it is possible your thread is still running
after your wait finishes, and then depending on the code, all
hell may break loose... This is mostly an issue with DLLs,
because it's possible after calling a shutdown function, the
client unloads the DLL, and later when the still running thread
is scheduled again, BOOM - access violation trying to fetch
the next instruction.

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: XXXX@XXXXX.COM
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================

"Senthilvel" < XXXX@XXXXX.COM > wrote in message
news:cc0re1$foc$ XXXX@XXXXX.COM ...
an
and
and
suspended
here.
pCb);
CoCreateInstance(CLSID_TestSTA,NULL,CLSCTX_LOCAL_SERVER,IID_ITestSTA,(void**
event,


 
 
 

Reentrancy problem ???

Post by Alexander » Sat, 03 Jul 2004 01:06:58

It looks to me like a proof of concept...

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: XXXX@XXXXX.COM
MVP VC FAQ: http://www.yqcomputer.com/
=====================================
 
 
 

Reentrancy problem ???

Post by Senthilve » Sat, 03 Jul 2004 12:23:45

>
Hi Igor,
I was looking into MessageFilters and wanted to simulate a problem that
could be solved using Message filters.
It may be that i have misunderstood the context and doing a wrong example.

But Igor after your post..
Assume i execute an asynchronous call(the way i did) and i want to hold the
clinet , till the synchronous call gets completed
What do i have to do ??? If the client dies then the call back pointer would
be invalid . Do i have to use the reference count for the Callback interface
and let the client to die only after the count goes to zero ????

Best Regards,
Senthilvel.
 
 
 

Reentrancy problem ???

Post by Senthilve » Sat, 03 Jul 2004 13:04:14

Ooops..alexander..Though i get to know good programming from you , i am not
able to grasp your message completely..
It will be very helpful if you can read along and answer a few queries i
have put inline...

But it does live in another process :) :) :)

In the client side, i make a call CoInitialize that creates an STA(lets name
it STA1).
This creates a invisible window and a message pump.(Let us call this
Thread2(the STA thread ,i hope)
and the thread on which the main executes as Thread1)
I then create an instance of the server using CoCreateInstance.
I think this has to create a object in the STA1 , but since my object lives
in another process and hence another apartment, that happens
to be an STA (lest call it STA2) there will be a proxy given back to STA1.
No i again create a Callback object , not using CoCreateInstance, but using
"new ".
1.Is this acceptable ?
2.If i create an object with new does this object gets created in an
apartment , here STA1 ??

When i call the method "CallMethod" , the Thread1 will enter the STA1(or it
has already entered???)
and the message will be posted to the Thread2 and the method will be invoked
on the Server's proxy.

Since the server is on another process, Callback pointer (passed as an
method parameter) will be marshaled
and unmarshaled in the STA2.So now STA2 contains the original object and the
proxy to the Callback interface.

To increase the complexity , i again create a STA(naming STA3) inside the
method call and marshal the Calllback pointer into STA3 and invoke the
method on it.

This call has to reenter the STA1 becaue thats the place where the original
Callback object resides.
Now the wait i am executing..is it on the
1.Thread1 or the Thread2 (STA thread) ????


Thanks Alexander, i'll keep it in mind hereafter...


But on the client side how do i wait on the thread handle???
I have not created any threads...



Know it quite a looong mail.. but your answers would sure throw a light
which would clear my undersatnding of COM..

Thanks and Best Regards,
Senthilvel.
 
 
 

Reentrancy problem ???

Post by Alexander » Sat, 03 Jul 2004 14:15:55

k, I got lost midstream, but I caught the reason for the
deadlock - I had missed the part about the callback. Indeed
your call makes it from the second client STA to the server,
but then when the server tries to call back, the call will never
make it, because your first STA is blocked. So you are
properly punished for blocking your STA :).

As for the thread question, maybe I misunderstood, but
my impression was you were waiting for the second STA
thread you created to signal you back before you exit.
That thread can only be created by you, hence you have
its handle, no?

--
=====================================
Alexander Nickolov
Microsoft MVP [VC], MCSD
email: XXXX@XXXXX.COM
MVP VC FAQ: http://www.mvps.org/vcfaq
=====================================
"Senthilvel" < XXXX@XXXXX.COM > wrote in message
news:cc2mke$j79$ XXXX@XXXXX.COM ...
not
name
lives
STA1.
using
it
invoked
the
original


 
 
 

Reentrancy problem ???

Post by anantp » Sat, 03 Jul 2004 18:42:58

enthilvel,
I am able to locate the deadlock, it's in step 7, see below steps. It
appears that in step 5 you put the ITestSTACb interface on
GIT(Marshal) and in step 7 you get it from GIT(UnMarshal). It never
comes out of this call
HRESULT hr = gpGIT->GetInterfaceFromGlobal(dwCookie,IID_ITestSTACb,(void**)&pCB);

Igor,Alexander; I am able to find out why GetInterfaceFromGlobal don't
return.
If i don't use GIT and hold ITestSTACb in static vaiable every thing
works fine.

Client STA1
1. Create ITestSTA instance |
2. Create ITestSTACb instance |
3. call ITestSTA->CallMethod(ITestSTACb) |
WaitForSingleObject |
4. inside CTestSTA::CallMethod(ITestSTACb) |
5. put ITestSTACb on GIT |
6. Create worker Thread ---------------------------> STA2
7. | Get ITestSTACb from GIT
8. | call ITestSTACb->Invoke
<---------------------------
9. ITestSTACb::Invoke() displays message |
10 Signal the event |
--------------------------->
11. | return to STA1
<---------------------------
12.Client Exit |


Thanks,
Anant








"Senthilvel" < XXXX@XXXXX.COM > wrote in message news:<cc0re1$foc$ XXXX@XXXXX.COM >...
 
 
 

Reentrancy problem ???

Post by Igor Tande » Sat, 03 Jul 2004 22:28:59


I'm confused. What is it after all - asynchronous or synchronous?
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
 
 
 

Reentrancy problem ???

Post by Igor Tande » Sat, 03 Jul 2004 22:50:55

Senthilvel" < XXXX@XXXXX.COM > wrote in message
news:cc2mke$j79$ XXXX@XXXXX.COM

Invisible window - yes. Message pump - no. It is your responsibility to
spin a message pump on the thread that enters STA. COM does not
magically generate one for you.


So you have two threads, each enters STA, and each spins a message pump,
right?


Your naming convention is a bit confusing, so let me try and get it
straight. In the client process, you have Thread1 running in STA1, and
Thread2 running in an unnamed STA apartment. Then you have a separate
server process whose thread runs in STA2. Did I get it?


Yes.


Yes. It's not really important which apartment the object was created
in. What's important is what apartment holds a direct interface pointer
to it, as opposed to a proxy. The notion of an object living in a
particular apartment only really becomes meaningful when you give out
its pointer to other apartments (which should get a proxy).


It's in STA1 the moment it called CoInitialize


How did Thread2 even come into the picture? Do you mean the thread in
the server process, the one running in STA2? Yes, a message is posted to
that one.


It's the other way round. The client invokes a method on the proxy it
has for the server object. The proxy then posts a message to the window
created in the server's thread, and enters a modal message pump waiting
for the server to respond. Eventually, a message pump running in the STA
apartment on the server picks the message and dispatches it to that
hidden window. The window proc then calls the real object. When this
call returns, the window proc posts a message back to Thread1. The proxy
running on Thread1 picks this message in its modal message pump, and
finally returns to the caller. This completes the exchange.


Right.


Right. The modal message pump spun by the proxy allows this, unless
IMessageFilter says otherwise.


What wait? The modal message pump? On Thread1 of course - that's the
thread that made an original outgoing call, right? You said yourself,
the call has to reenter STA1, which is the apartment running on Thread1.
What does Thread2 have to do with it all? It has not made any COM calls
as far as I can see from your description, so it appears to be
irrelevant.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken


 
 
 

Reentrancy problem ???

Post by Igor Tande » Sat, 03 Jul 2004 23:00:44


Correction - _appears_ to work fine. You give out a direct interface
pointer to multiple threads. Now your object can be called
simultaneously from these threads. But it explicitly claims that it is
not thread-safe, by marking itself apartment-threaded. So you set
yourself up for hard-to-diagnose threading problems, race conditions and
such.


Verbotten. STA thread _must_ _not_ _block_ .


At this point, GIT must coordinate with the original STA1 thread to
correctly marshal the pointer. It expects STA1 to sit in a message pump
and wait for requests, as a nice well-behaved STA thread must. Instead,
your STA1 thread blocks. GIT also blocks waiting for STA1 to respond to
the message GIT just sent to it. So the two cheerfully block waiting for
each other. Hence your deadlock.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
 
 
 

Reentrancy problem ???

Post by Senthilve » Tue, 06 Jul 2004 15:20:15


Thanks Igor..
I was under the understanding that in addition to the invisible window ,an
thread and a message queue will also be created for the
STA on a COM created thread(this is the thread i referred as Thread2..my
error!!!!)I never thought that the message pump is to be created on my
thread(Thread1) .

Best Regards,
Senthilvel.
 
 
 

Reentrancy problem ???

Post by Igor Tande » Wed, 07 Jul 2004 22:40:54


Yes, when COM automatically creates an STA thread for marshaling
purposes, this thread runs a message pump. When your own thread enters
STA by calling CoInitialize, you need to code a message pump for it.


You need one, if you want it to be an STA thread.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken