Зависание out-of-proc COM сервера
От: KAdot  
Дата: 04.02.13 10:40
Оценка:
Есть out-of-proc COM сервер с COM синглетоном Engine, реализованным с помощью стандартного DECLARE_CLASSFACTORY_SINGLETON. Работает в STA (CComSingleThreadModel, _ATL_APARTMENT_THREADED).

Клиенты COM сервера:
1. ActiveScript (JScript), в который с помощью AddNamedItem передается ссылка на Engine.
2. Несколько независимых IE BHO, работающих в STA.

BHO периодически вызывают у Engine метод DispatchEvent (по событиям браузера передавая свой объект), Engine в свою очередь сразу же вызывает JavaScript функцию (или несколько) внутри ActiveScript.
Эта схема отлично работает до тех пор, пока не включишь сразу два BHO (если включить один любой из BHO то все ок).

Если включить два BHO, на вызове функции в ActiveScript (с помощью IDispatch/Invoke) в случайные моменты происходит зависание.
Дополнительные потоки мною не создаются.

Некоторые наблюдения:
— Если в ActiveScript не передавать объект пришедший из BHO, а подменить его на такой же, но созданный внутри Engine, зависания нет.
— Зависание происходит только при срабатывании сборщика мусора в ActiveScript при попытке сделать Release объекту, переданному из BHO (IUnknown_Release_Proxy в коллстеке).

Колстек зависания:

>    ntdll.dll!_ZwWaitForMultipleObjects@20()  + 0x15 bytes    
     ntdll.dll!_ZwWaitForMultipleObjects@20()  + 0x15 bytes    
     KernelBase.dll!_WaitForMultipleObjectsEx@20()  + 0x100 bytes    
     kernel32.dll!_WaitForMultipleObjectsExImplementation@20()  + 0x8e bytes    
     user32.dll!_RealMsgWaitForMultipleObjectsEx@20()  + 0xe2 bytes    
     ole32.dll!CCliModalLoop::BlockFn(void * * ahEvent, unsigned long cEvents, unsigned long * lpdwSignaled)  Line 1222    C++
     ole32.dll!ModalLoop(CMessageCall * pcall)  Line 211    C++
     ole32.dll!ThreadSendReceive(CMessageCall * pCall)  Line 4979    C++
     ole32.dll!CRpcChannelBuffer::SwitchAptAndDispatchCall(CMessageCall * * ppCall)  Line 4454 + 0x6 bytes    C++
     ole32.dll!CRpcChannelBuffer::SendReceive2(tagRPCOLEMESSAGE * pMessage, unsigned long * pstatus)  Line 4076    C++
     ole32.dll!CCliModalLoop::SendReceive(tagRPCOLEMESSAGE * pMsg, unsigned long * pulStatus, IInternalChannelBuffer * pChnl)  Line 899 + 0x17 bytes    C++
     ole32.dll!CAptRpcChnl::SendReceive(tagRPCOLEMESSAGE * pMsg, unsigned long * pulStatus)  Line 583 + 0xd bytes    C++
     ole32.dll!CCtxComChnl::SendReceive(tagRPCOLEMESSAGE * pMessage, unsigned long * pulStatus)  Line 734 + 0xa bytes    C++
     ole32.dll!NdrExtpProxySendReceive(void * pThis, _MIDL_STUB_MESSAGE * pStubMsg)  Line 1932    C++
     rpcrt4.dll!@NdrpProxySendReceive@4()  + 0xe bytes    
     rpcrt4.dll!_NdrClientCall2()  + 0x144 bytes    
     ole32.dll!ObjectStublessClient(void * ParamAddress, long Method)  Line 474 + 0x8 bytes    C++
     ole32.dll!_ObjectStubless@0()  Line 154    Asm
     ole32.dll!RemoteReleaseRifRefHelper(IRemUnknown * pRemUnk, int fReleaseRemUnkProxy, int fProcessingPostedMessage, OXIDEntry * pOXIDEntry, unsigned short cRifRef, tagREMINTERFACEREF * pRifRef, IUnknown * pAsyncRelease)  Line 6770 + 0xc bytes    C++
     ole32.dll!RemoteReleaseRifRef(CStdMarshal * pMarshal, OXIDEntry * pOXIDEntry, unsigned short cRifRef, tagREMINTERFACEREF * pRifRef)  Line 6694    C++
     ole32.dll!CStdMarshal::DisconnectCliIPIDs()  Line 3964    C++
     ole32.dll!CStdMarshal::Disconnect(unsigned long dwType)  Line 3273    C++
     ole32.dll!CStdIdentity::~CStdIdentity()  Line 312    C++
     ole32.dll!CStdIdentity::`scalar deleting destructor'()  + 0xd bytes    C++
     ole32.dll!CStdIdentity::CInternalUnk::Release()  Line 767    C++
     ole32.dll!IUnknown_Release_Proxy(IUnknown * This)  Line 1773    C++
     oleaut32.dll!_VariantClear@4()  + 0xac9 bytes    
     jscript.dll!VAR::Clear()  + 0x50 bytes    
     jscript.dll!GcAlloc::ReclaimGarbage()  + 0xa2 bytes    
     jscript.dll!GcContext::Reclaim()  + 0x8e bytes    
     jscript.dll!GcContext::CollectCore()  - 0x72f bytes    
     jscript.dll!GcContext::Collect()  + 0x34 bytes    
     jscript.dll!CScriptRuntime::Run()  - 0x864f bytes    
     jscript.dll!ScrFncObj::CallWithFrameOnStack()  + 0xf3 bytes    
     jscript.dll!ScrFncObj::Call()  + 0x84 bytes    
     jscript.dll!NameTbl::InvokeInternal()  + 0x113 bytes    
     jscript.dll!VAR::InvokeByDispID()  + 0x73 bytes    
     jscript.dll!CScriptRuntime::Run()  + 0x1d89 bytes    
     jscript.dll!ScrFncObj::CallWithFrameOnStack()  + 0xf3 bytes    
     jscript.dll!ScrFncObj::Call()  + 0x84 bytes    
     jscript.dll!NameTbl::InvokeInternal()  + 0x113 bytes    
     jscript.dll!VAR::InvokeByDispID()  + 0x73 bytes    
     jscript.dll!CScriptRuntime::Run()  + 0x1d89 bytes    
     jscript.dll!ScrFncObj::CallWithFrameOnStack()  + 0xf3 bytes    
     jscript.dll!ScrFncObj::Call()  + 0x84 bytes    
     jscript.dll!NameTbl::InvokeInternal()  + 0x12c6 bytes    
     jscript.dll!VAR::InvokeByDispID()  + 0x73 bytes    
     jscript.dll!NameTbl::GetVal()  + 0x3b bytes
out-of-proc com atl sta activescript
Re: Зависание out-of-proc COM сервера
От: rus blood Россия  
Дата: 05.02.13 15:48
Оценка:
Здравствуйте, KAdot, Вы писали:

KA> Engine в свою очередь сразу же вызывает JavaScript функцию (или несколько) внутри ActiveScript.


Инстанс ActiveScript создается на каждый вызов Engine из BHO, или он общий на всех?
Если общий, то возможно проблема в STA-модели WSH, нужно делать маршаллинг и крутить циклы сообщений...
Имею скафандр — готов путешествовать!
Re[2]: Зависание out-of-proc COM сервера
От: KAdot  
Дата: 06.02.13 04:08
Оценка:
Здравствуйте, rus blood, Вы писали:

RB>Инстанс ActiveScript создается на каждый вызов Engine из BHO, или он общий на всех?

ActiveScript создается один раз и он общий для всех клиентов.

RB>Если общий, то возможно проблема в STA-модели WSH, нужно делать маршаллинг и крутить циклы сообщений...

Маршаллинг между ActiveScript и Engine? В какое место добавить цикл сообщений? На сколько я понимаю, он уже и так есть в ATL::CAtlExeModuleT.
Re[3]: Зависание out-of-proc COM сервера
От: rus blood Россия  
Дата: 06.02.13 13:01
Оценка:
Здравствуйте, KAdot, Вы писали:

KA>ActiveScript создается один раз и он общий для всех клиентов.

KA>Маршаллинг между ActiveScript и Engine? В какое место добавить цикл сообщений? На сколько я понимаю, он уже и так есть в ATL::CAtlExeModuleT.


Пардон, невнимательно прочитал исходной сообщение.

Есть out-of-proc COM сервер с COM синглетоном Engine, реализованным с помощью стандартного DECLARE_CLASSFACTORY_SINGLETON. Работает в STA (CComSingleThreadModel, _ATL_APARTMENT_THREADED).


Ну CComSingleThreadModel, _ATL_APARTMENT_THREADED — это опции построения компоненты.
Реальный апартмент будет определяться настройками и конфигурированием.
Кто хостит COM-объект — COM+, exe-сервер или сервис?
Синглетон действительно в одном потоке живет, проверял?
Имею скафандр — готов путешествовать!
Re[4]: Зависание out-of-proc COM сервера
От: KAdot  
Дата: 07.02.13 06:46
Оценка:
Здравствуйте, rus blood, Вы писали:

RB>Синглетон действительно в одном потоке живет, проверял?

Да, выводил при вызове методов синглтона лог с Thread ID — всегда был одинаков.

Разобрался более подробно как именно происходит дедлок:
1. Клиент вызывает Engine::dispatchEvent (через Invoke), и пытается дождаться ответа сервера (в коллстеке oleaut32.dll!_IDispatch_Invoke_Proxy, user32.dll!_RealMsgWaitForMultipleObjectsEx).
2. До сервера доходит запроса и он передает объект в JScript, в нем срабатывает сборщик мусора и вызывается Release у старых объектов, переданных ранее из клиента (IUnknown_Release_Proxy, MsgWaitForMultipleObjects).
3. В итоге имеем, что клиент ждет ответа сервера, в этот момент сервер начинает ждать ответа клиента и все зависает.
4. На сколько мне известно COM должен корректно отрабатывать такие ситуации, используя MsgWaitForMultipleObjects для прокачки сообщения во время ожидания, но тем не менее дедлок все равно происходит.

В какую сторону копать?
Re[5]: Зависание out-of-proc COM сервера
От: rus blood Россия  
Дата: 07.02.13 10:55
Оценка:
Здравствуйте, KAdot, Вы писали:

KA>1. Клиент вызывает Engine::dispatchEvent (через Invoke), и пытается дождаться ответа сервера (в коллстеке oleaut32.dll!_IDispatch_Invoke_Proxy, user32.dll!_RealMsgWaitForMultipleObjectsEx).

KA>2. До сервера доходит запроса и он передает объект в JScript, в нем срабатывает сборщик мусора и вызывается Release у старых объектов, переданных ранее из клиента (IUnknown_Release_Proxy, MsgWaitForMultipleObjects).
KA>3. В итоге имеем, что клиент ждет ответа сервера, в этот момент сервер начинает ждать ответа клиента и все зависает.
KA>4. На сколько мне известно COM должен корректно отрабатывать такие ситуации, используя MsgWaitForMultipleObjects для прокачки сообщения во время ожидания, но тем не менее дедлок все равно происходит.

Т.е., ситуация такая.
Клиент создает инстанс синглетона и еще какие-то объекты, которые сам хостит.
Клиент вызывает метод синглетона в который передает эти объекты.
Синглетон вызывает скриптинг, из которого дергаются какие-то методы переданных объектов.

Так?
Тогда ситуация пункта 3 у тебя должна возникать постоянно...
А можешь проверить, в каких потоках "живут" объекты на клиентской стороне?

По поводу MsgWaitForMultipleObjects можно через IMessageFilter посмотреть, есть входящие обращения к клиенту во время вызова метода.

Также, полагаю, выгружать скриптинг после завершения метода сниглетона для тебя не выход...
Имею скафандр — готов путешествовать!
Re[6]: Зависание out-of-proc COM сервера
От: KAdot  
Дата: 08.02.13 06:59
Оценка:
Здравствуйте, rus blood, Вы писали:

RB>Т.е., ситуация такая.

RB>Клиент создает инстанс синглетона и еще какие-то объекты, которые сам хостит.
RB>Клиент вызывает метод синглетона в который передает эти объекты.
RB>Синглетон вызывает скриптинг, из которого дергаются какие-то методы переданных объектов.
Да.

RB>А можешь проверить, в каких потоках "живут" объекты на клиентской стороне?

На клиенте 1 всегда свой поток №1. CoGetApartmentType возвращает APTTYPE = APTTYPE_STA.
На клиенте 2 всегда свой поток №2. CoGetApartmentType возвращает APTTYPE = APTTYPE_STA.
На сервере всегда свой поток №3. CoGetApartmentType возвращает APTTYPE = APTTYPE_MAINSTA.

RB>По поводу MsgWaitForMultipleObjects можно через IMessageFilter посмотреть, есть входящие обращения к клиенту во время вызова метода.

Спасибо, попробую.

RB>Также, полагаю, выгружать скриптинг после завершения метода сниглетона для тебя не выход...

В ActiveScript находится логика по обработке событий от клиентов и выгружать его нельзя.
Re[7]: Зависание out-of-proc COM сервера
От: KAdot  
Дата: 08.02.13 08:19
Оценка:
Здравствуйте, KAdot, Вы писали:

KA>На клиенте 2 всегда свой поток №2. CoGetApartmentType возвращает APTTYPE = APTTYPE_STA.

Подебажил еще, из-за особенностей реализации все-же в клиенте №2 могут быть разные потоки.
Re[8]: Зависание out-of-proc COM сервера
От: KAdot  
Дата: 08.02.13 11:02
Оценка:
Здравствуйте, KAdot, Вы писали:

KA>Подебажил еще, из-за особенностей реализации все-же в клиенте №2 могут быть разные потоки.

Попробовал в клиенте 2 переместить все в один поток — не помогло.
Re[9]: Зависание out-of-proc COM сервера
От: KAdot  
Дата: 19.02.13 07:06
Оценка:
Больше деталей реализации:
// Engine (out of process COM singleton)

class ATL_NO_VTABLE CEngine :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CEngine, &CLSID_Engine>,
    public IDispatchImpl<IEngine, &IID_IEngine, &LIBID_EngineLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
    
    DECLARE_CLASSFACTORY_SINGLETON(CEngine)
    
    STDMETHOD(dispatchEvent)(BSTR name, IDispatch* pEvent, VARIANT_BOOL* pbSuccess)
    {
        // pEvent is CPropertyStore instance
        ActiveScriptDispatch.Invoke1(L"FuncName", pEvent, &varResult);
    }
}


// BHO

class CPropertyStore :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CPropertyStore, &CLSID_NULL>,
    public IDispatch
{
    BEGIN_COM_MAP(CPropertyStore)
        COM_INTERFACE_ENTRY(IUnknown)
        COM_INTERFACE_ENTRY(IDispatch)
    END_COM_MAP()
    
    BOOL SetProperty(CString strName, VARIANT *value)
    {
        // Store value in CAtlArray
    }
    
    // IDispatch impl
    STDMETHOD(GetTypeInfoCount)(UINT *pctinfo);
    STDMETHOD(GetTypeInfo)(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
    STDMETHOD(GetIDsOfNames)(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    STDMETHOD(Invoke)(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, 
        VARIANT *pVarResult,EXCEPINFO *pExcepInfo, UINT *puArgErr);
}

class ATL_NO_VTABLE CBHO :
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CBHO, &CLSID_BHO>,
    public IObjectWithSiteImpl<CBHO>,
    public IDispatchImpl<IBHO, &IID_IBHO, &LIBID_Lib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
    public IDispEventImpl<1, CBHO, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 0>
{
    void onEvent(...)
    {
        if(m_pEngine == NULL && SUCCEEDED(m_pEngine.CoCreateInstance(CLSID_Engine)))
        {
            CComObject<CPropertyStore> *pEvent = NULL;
            HRESULT hRes = CComObject<CPropertyStore>::CreateInstance(&pEvent);

            CComVariant varEvent(pEvent);
            CComVariant varName(L"EventName");
            CComVariant varResult;

            m_pEngine.Invoke2(L"dispatchEvent", &varName, &varEvent, &varResult);
        }
    }
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.