We have some COM+ code written in everybody's favourite language(?) -- VB6. This COM+ component calls into a standard COM component written by a 3rd party which performs calls into a SQL Server database. We don't do anything fancy in the COM+ -- nothing more than (this is an example only; we don't really call our functions doStuff :-) ):
Dim o As Library.Object
Set o = New Library.Object
str = o.DoSomething()
Set o = Nothing
doStuff = str
As a horrendously quick stress test, we've wrapped the call up in a drop-dead simple VBScript that simple creates object, calls method in a loop, sets object to nothing, and repeats this a few times. We then run four of these concurrently within a command prompt.
What we experience is the four COM+ windows stopping dead as if they've blocked each other in some way. Based on the behaviour of the output it looks like the different windows are sharing instances of objects somewhere along the way: for example, the speed at which the output appears between windows syncs between the windows ... so two can be blistering along at speed, whilst the other two spit out a line a second (and when they spit the line out, they do so at the same time).
Then ultimately, all four windows seem to stop dead - within component services, we see the Call Time start to climb (so from the few milliseconds per call, it climbs to 30, 40 seconds). Sometimes dllhost.exe fails and we get a COM Surrogate error dialog appear (at which point the window recovers as a new dllhost is spawned).
There is no activity on the database, so we've ruled out that things are blocking at the database layer. We seem to have achieved better results by setting the COM+ component to "Transactions: disabled", but the hangs don't disappear. Rather than
new, we're going to try creating the COM object with
CreateObject to see what that does (if anything). Objects are set to Nothing once finished with both in COM+ and at the VBScript layer.
Worth noting that if the 3rd party library is called directly from VBScript (bypassing COM+), no issues occur. Thus it seems as if it's something to do with the way that COM+ interacts with COM objects, but other than fiddling with different settings under the objects properties in Component Services, not sure really what else is happening.
Any suggestions as to what's happening under the hood to cause this? Or settings to tweak?
In answer to questions in answers:
It looks like it's a sync issue somewhere deep in COM+ or COM. In our test script, if we add a random delay of 10-50ms on each iteration, the problem disappears. If we have a fixed delay, we lock. Some googling around seems to show that it can be a problem on heavily loaded COM+ with an STA, which is documented here on an MS blog. It'd be nice to go back to a Server 2000 box or a Server 2003 SP1 box: that might have to be the next thing to see...
It sounds like you are potentially hitting an issue with COM+ and STA out of apartment calls.
Microsoft used to have a great article published by Michael McKeown called "Preserving Application Performance When Porting from MTS to COM+" discussing this but it looks it has been removed (there is an archived version here).
Basically, the COM+ STA thread pool binds up to 5 activities to each STA thread. When you make an out of apartment call (3rd party component or SQL Server) COM+ allows other requests to be serviced as another activity on the STA thread. This can happen for up to 5 activities (per thread). Also once control has been given to another activity the original activity cannot regain control until the second activity is complete. Under heavy load and/or if the calls are "long running" then the time for the first activity to complete is the sum of the times for all of the other activities (on the thread) to complete. This can kill your performance.
If you are able to switch the setting for your entire COM+ server you can configure COM+ to use the old MTS 100 STA thread approach. See Registry key for tuning COM+ thread and activity for the details. You can see if that helps your performance. The other approach would be to avoid STA components.
Perhaps COM+ Object Pooling Concepts and related articles like Configuring a Component to Be Pooled will be helpful.
Poolable objects must meet certain requirements to enable a single object instance to be used by multiple clients. For example, they can't hold client state or have any thread affinity.
2 things come to mind:
Have you tried making the
o variable local instead of module level?
Dim o as Library.Object
Set o = New Library.Object str = o.DoSomething() Set o = Nothing doStuff = str End Function
Are you that the Library.Object component and the .DoSomething method does not contain global variables (or MessageBox statements)?
Can you throw logging statements after each line to see where the code chokes?
Hit up ProcMon to see when it stops hitting the registry. Did the last call fail? If so, to where?