COM Interop + Threading + Scalability
- From: "Logu Krishnan" <listmail@xxxxxxxxxxxxxxxx>
- Date: Mon, 12 Sep 2005 23:49:18 +0530
Hi All,
Recently, I've been working on a web app that requires a VB6 Component to be
reused, in the ASP.NET App (We're migrating). The Client completely denies
to move the VB6 Component to a .NET Component, as they have invested heavily
Money+Time+Human for a long time for building and enriching this
component(Around 2.5 yrs).
While, we were discussing this architectural issue, my senior architect
posed me with the following issue And me incidentally being always on
purely managed world for quite some time, is researching a bit on this,
also, i thought i shall get some suggestions from any of the experienced
hands here...so that i shall not miss something stupid... [Err... I started
to hate mixing Managed + Unmanaged world....]
here is the issue in its initial draft version... [it's bit long...sorry
:) ]
---------------------------------------------------------------------------
Background
------------
The Software will be implemented as a web-based ASP.NET application. This
application will rely on the Rules Processor component in the existing
product. The Rules Processor is implemented in VB 6 and the ASP.NET
application will interact with this component using the COM Interop
facilities available in .NET. As the Rules Processor will reside on the
server, the solution will need to cater to the situation where there are
multiple concurrent requests for the Rules Processor.
Potential Scalability Issue
-------------------------
COM components implemented in VB 6 are single threaded. Therefore, STA
(Single Threaded Apartment) is the only supported COM Threading Model for
such components. In a situation where multi-threaded clients or multiple
clients can simultaneously invoke operations on the COM component, STA
components can become a serious bottleneck, either due to the serial
execution of client requests (assuming there is only one instance of the
component on the server), or due to the cost of having a dedicated instance
of the component for each concurrent client.
During runtime, the .NET CLR, more specifically, the Runtime Callable
Wrapper (RCW), creates and initializes an apartment when calling a COM
object. Managed threads in the CLR can create different types of apartments
and this is determined directly by the value of the
System.Threading.ApartmentState property of the managed thread that creates
the apartment. Thus, as an example, if the thread's
System.Threading.ApartmentState property is set to "MTA", an MTA apartment
is created when calling a COM component. Note that all managed threads in
ASP.NET have this property marked as "MTA" by default.
When a COM apartment and a managed-thread-generated apartment are compatible
(in our case, this means both are STA), COM allows the calling thread to
invoke the operations of the COM object directly. Thus, the code in the COM
object's member gets executed by the managed thread itself. However, if the
threading model of the caller thread does not match the apartment type
expected by the COM component, it results in the following two overheads:
Thread Context Switch:
------------------------
What this means is that the COM runtime blocks the current thread, marshals
the parameters to the thread that owns the STA (this is a thread which the
COM runtime creates when it detects the apartment type mismatch) using a
Windows message pump, which then gets acted on by the owning STA thread. A
similar marshalling overhead takes place on the return path as well.
Inter-Apartment Marshalling:
The COM runtime introduces the overhead of a stub and proxy for making this
inter-apartment call. I am not sure if this overhead is really a part of the
above item, instead of being some additional overhead itself. Note, this is
not the marshalling of the non-blittable data types which is something that
will always be there in all COM interop situations, unless all data types on
the COM interface are blittable.
It is possible to avoid the above overheads by making sure that the managed
thread's threading model is set appropriately before it creates the COM
component. As noted above, ASP.NET managed threads are marked "MTA" by
default. However, the ASP.NET runtime does manage a pool of "STA threads"
which are used for processing HTTP client requests provided you have set
"ASPCOMPAT=true" in you ASP.NET pages. Since the STA threads get used only
for processing user requests and events, it is essential that the COM
component be created in one of the page handlers, like Page_Init() or
Page_Load() instead of in the Page constructor which will get executed in
the context of an ASP.NET MTA thread.
Now, note that COM guarantees that STA objects will be executed on the
thread in which they were created regardless of the thread in which they are
called. In other words, COM components living in STAs have thread-affinity
and a request on any other thread than the thread that actually created the
apartment (and owns the apartment) will mean the overhead of a Thread
Context Switch (nearly as bad as a process context switch). Keeping this in
mind, and the implications of setting "ASPCOMPAT=true", the above mentioned
overhead can be eliminated only if for the entire lifetime of the COM
object, all requests are made in the same thread. Since we don't have any
control on which thread from the ASP.NET thread pool services consecutive
requests from a given client, this means that we should restrict the
lifetime of a COM object within a single HTTP request-response cycle if we
are to avoid the above overhead. In other words, create and destroy the MDC
Rules Processor component in each request-response cycle that needs its
services. While it is well understood that we will need to have multiple
instances of the COM component on the server for scalability, this frequent
creation-destruction means a lot of overhead and limitations:
1. COM Component Creation and Initialization
2. COM Component Destruction and GC
3. High memory usage on load.
4. Inability to have stateful interactions with the component spanning
multiple client requests.
Potential Solution
------------------
Due to the inherent single-threaded nature of VB 6 components, it is
required to have multiple instances of the Rules Processor component - each
running in its own apartment with its own copy of any global data (need to
find out if this also includes "static" variables or is it just public
variables). However, the problem with needing to create a new instance for
each request-response cycle as mentioned above might have a solution in
maintaining a map that associates each STA thread in the ASP.NET runtime to
its corresponding component instance. This map can be looked up in each
request-response cycle using the current-thread as the key to obtain the
handle to the appropriate COM component. However, this can work only if the
interaction with the Rules Processor is stateless across the requests.
In the situation where the interaction with the Rules Processor needs to be
indeed stateful, the map should associate the session (not the thread) with
the component instance, but since we have no control of which STA thread
processes which user request, the overheads of a thread context switch is
inescapable. In other words, we are royally screwed!
--------------------------------------------------
Am I Missing something here totally.... Thougts Please .... !
Thanks
Regards,
Logu Krishnan
http://blog.logukrishnan.net
.
- Follow-Ups:
- Re: COM Interop + Threading + Scalability
- From: Joseph Geretz
- Re: COM Interop + Threading + Scalability
- Prev by Date: Re: Callback from native C++ to managed C++
- Next by Date: Registering .Net Assembly using Regasm doesn't work right
- Previous by thread: Simply Call a method from a OCX?
- Next by thread: Re: COM Interop + Threading + Scalability
- Index(es):
Relevant Pages
|