HighTechTalks DotNet Forums  

ServicedComponent.Dispose(): instance not returned to pool if passed to a server app beforehand

Dotnet Framework (Component Services) microsoft.public.dotnet.framework.component_services


Discuss ServicedComponent.Dispose(): instance not returned to pool if passed to a server app beforehand in the Dotnet Framework (Component Services) forum.



Reply
 
Thread Tools Search this Thread Display Modes
  #1  
Old   
Toni Arnold
 
Posts: n/a

Default ServicedComponent.Dispose(): instance not returned to pool if passed to a server app beforehand - 10-11-2006 , 10:21 AM






Hi

I'm unsure whether this is a new phenomena caused by some service pack or
whatever, but I recently discovered a serious problem with a
ServicedComponent running in production in this configuration since half a
year: A pooled ServicedComponent instance correctly disposed is not returned
to the pool if it beforehand was passed to another, non-pooled
ServicedComponent as a member of a method argument object. Nulling out the
member field of the object solves the problem, but I don't really like to
solve problems that simply must not exist.

Environment: VB.NET 2003 sp1, .NET 1.1 sp1, win2k

Let me explain the situation: There is an asp.net web page "asp.dll" using a
library "lib.dll" and a server-side ServicedComponent "com.dll".
There is an asp.main() method, a lib.ModelClass and two ServicedComponent
classes, com.Worker and com.Cache:


com.dll (references lib.dll)
-------
<Assembly: ApplicationActivation(ActivationOption.Server)>

<JustInTimeActivation(False), EventTrackingEnabled(True), _
Transaction(TransactionOption.NotSupported)> _
Public Class Worker
Inherits ServicedComponent
Public Function Calculate(ByVal pObjCrosser As ModelClass) As ModelClass
' calculate data in pObjCrosser and serialize the result object
' back from dllhost do aspnet_wp
Return pObjCrosser
End Function
End Class

<ObjectPooling(MinPoolSize:=4, MaxPoolSize:=16, CreationTimeout:=10000), _
JustInTimeActivation(False), EventTrackingEnabled(True), _
Transaction(TransactionOption.NotSupported)> _
Public Class Cache
Inherits ServicedComponent
Protected Overrides Function CanBePooled() As Boolean
Return True
End Function
End Class


lib.dll (cannot reference com.dll, therefore separated ICache interface)
-------
<Serializable()>Class ModelClass
Public WriteOnly Property ObjCache As ICache
Set(ByVal Value As ICache)
Me.mObjCache = ObjCache ' <--- this assignment is the culprit!
End Set
End Property
End Class


asp.dll (references both com.dll and lib.dll)
-------

Sub Main()
ObjCrosser = New ModelClass
Try
ObjWorker = New Worker ' 1: non-pooled ServicedComponent
Try
ObjCache = New Cache ' 2: pooled ServicedComponent
ObjCrosser.ObjCache = ObjCache ' 3: assign it to an object
ObjWorker.Calculate(ObjCrosser) ' 4: serialize it as a member to the
COM+ server
Finally
ObjCache.Dispose() ' 5: deactivate, try to send back to the pool
End Try
Finally
ObjWorker.Dispose() ' 6: deactivate
End Try


Now, when debugging, the following happens in the Component Services
Management Console. A debug Watch has been placed retrieving a value from
ObjCache.

1:
- com.dll gets started
- 4 Cache objects are now in Pool (MinPoolSize), but not Activated yet.
- Worker gets Activated, but is not In Call yet.

2:
- 1 Cache object is taken from the Pool, it is Activated now.
- Watch now retrieves a value from the active object.

3:
- ObjCrosser has now the Cache object as a private member.
Nothing new happens on the COM+ server, as expected.

4:
- ObjCrosser is serialized to the COM+ Server, method call is too short
to show up in Activated, but Call Time is about 30ms.
Nothing else happens on the COM+ server, as expected.

5:
- Watch throws a System.ObjectDisposedException, as expected.

Until now, anything has worked exactly as expected, but now:
- ObjCached, while evidentially disposed, is still Activated and *not*
returned to the pool!

6:
- ObjWorker is no more Activated, as expected.

Now the page terminates, but the Cache object remains Activated until an
idle shutdown event happens. Consequence: Once the MaxPoolSize limit has
been hit (here after 16 page requests without an idle shutdown), the server
consecutively responds with "System.Runtime.InteropServices.COMException
COM+ activation failed because the activation could not be completed in the
specified amount of time." - until the COM+ application is restarted.


The Main() method may look awful in this example. In fact, the ModelClass
and the Worker are heavyweight model tier classes, the former requiring the
asp.net context to be instantiated, the latter requiring such an instance to
do the real work required to create the web page. The current method call
layout is the result of performance optimizing by minimizing the number of
cross-COM+-boundary method calls which just are too expensive in inner
loops: the Worker class (causing thousands of queries to the Cache class)
has been moved from the aspnet_wp process to the dllhost. The "evil" Cache
member in ObjCrosser of course is not used within the Calculate() call, it
just happens accidentally to be there (in fact, the member is overwritten
with one instantiated within the Worker), but needed in the snipped away
code before and after. By the way: Exchanging the order of
instantiate/dispose of the two ServicedComponents doesn't change the
described behaviour.

Of course the actual leak is trivial to elimiate (after spending endless
hours on debugging): I just have to add the line
ObjCrosser.ObjCache = Nothing
between steps 3 and 4. But my main problem is that

1) Pooled objects are by design a limited resource (a *hard* limit!).
2) Try/Finally is therefore required to guarantee that they get disposed in
*any* (yes, *any*!) case.
3) But a successfull call to .Dispose() *demonstrably* does *not* dispose
the object in all cases.

So I have two questions:

A) Is the described behaviour by design and documented somewhere, or is that
a (known?) bug?
B) Are there other known cases where ServicedComponent.Dispose() actually
doesn't to the job it is *absolutely required* to do?




Reply With Quote
  #2  
Old   
Bryan Phillips
 
Posts: n/a

Default Re: ServicedComponent.Dispose(): instance not returned to pool if passed to a server app beforehand - 10-29-2006 , 03:02 PM






If setting the variable to null releases it back to the pool, then the
dispose method is not calling Marshal.ReleaseCOMObject on itself like it
does for proxies. Try adding the call to Marshal.ReleaseCOMObject right
after you call dispose.

Bryan Phillips
MCSD, MCDBA, MCSE
Blog: http://bphillips76.spaces.live.com




"Toni Arnold" <tony.arnold (AT) comparis (DOT) ch> wrote


Quote:
Hi

I'm unsure whether this is a new phenomena caused by some service pack or
whatever, but I recently discovered a serious problem with a
ServicedComponent running in production in this configuration since half a
year: A pooled ServicedComponent instance correctly disposed is not returned
to the pool if it beforehand was passed to another, non-pooled
ServicedComponent as a member of a method argument object. Nulling out the
member field of the object solves the problem, but I don't really like to
solve problems that simply must not exist.

Environment: VB.NET 2003 sp1, .NET 1.1 sp1, win2k

Let me explain the situation: There is an asp.net web page "asp.dll" using a
library "lib.dll" and a server-side ServicedComponent "com.dll".
There is an asp.main() method, a lib.ModelClass and two ServicedComponent
classes, com.Worker and com.Cache:


com.dll (references lib.dll)
-------
Assembly: ApplicationActivation(ActivationOption.Server)

JustInTimeActivation(False), EventTrackingEnabled(True), _
Transaction(TransactionOption.NotSupported)> _
Public Class Worker
Inherits ServicedComponent
Public Function Calculate(ByVal pObjCrosser As ModelClass) As ModelClass
' calculate data in pObjCrosser and serialize the result object
' back from dllhost do aspnet_wp
Return pObjCrosser
End Function
End Class

ObjectPooling(MinPoolSize:=4, MaxPoolSize:=16, CreationTimeout:=10000), _
JustInTimeActivation(False), EventTrackingEnabled(True), _
Transaction(TransactionOption.NotSupported)> _
Public Class Cache
Inherits ServicedComponent
Protected Overrides Function CanBePooled() As Boolean
Return True
End Function
End Class


lib.dll (cannot reference com.dll, therefore separated ICache interface)
-------
Serializable()>Class ModelClass
Public WriteOnly Property ObjCache As ICache
Set(ByVal Value As ICache)
Me.mObjCache = ObjCache ' <--- this assignment is the culprit!
End Set
End Property
End Class


asp.dll (references both com.dll and lib.dll)
-------

Sub Main()
ObjCrosser = New ModelClass
Try
ObjWorker = New Worker ' 1: non-pooled ServicedComponent
Try
ObjCache = New Cache ' 2: pooled ServicedComponent
ObjCrosser.ObjCache = ObjCache ' 3: assign it to an object
ObjWorker.Calculate(ObjCrosser) ' 4: serialize it as a member to the
COM+ server
Finally
ObjCache.Dispose() ' 5: deactivate, try to send back to the pool
End Try
Finally
ObjWorker.Dispose() ' 6: deactivate
End Try


Now, when debugging, the following happens in the Component Services
Management Console. A debug Watch has been placed retrieving a value from
ObjCache.

1:
- com.dll gets started
- 4 Cache objects are now in Pool (MinPoolSize), but not Activated yet.
- Worker gets Activated, but is not In Call yet.

2:
- 1 Cache object is taken from the Pool, it is Activated now.
- Watch now retrieves a value from the active object.

3:
- ObjCrosser has now the Cache object as a private member.
Nothing new happens on the COM+ server, as expected.

4:
- ObjCrosser is serialized to the COM+ Server, method call is too short
to show up in Activated, but Call Time is about 30ms.
Nothing else happens on the COM+ server, as expected.

5:
- Watch throws a System.ObjectDisposedException, as expected.

Until now, anything has worked exactly as expected, but now:
- ObjCached, while evidentially disposed, is still Activated and *not*
returned to the pool!

6:
- ObjWorker is no more Activated, as expected.

Now the page terminates, but the Cache object remains Activated until an
idle shutdown event happens. Consequence: Once the MaxPoolSize limit has
been hit (here after 16 page requests without an idle shutdown), the server
consecutively responds with "System.Runtime.InteropServices.COMException
COM+ activation failed because the activation could not be completed in the
specified amount of time." - until the COM+ application is restarted.


The Main() method may look awful in this example. In fact, the ModelClass
and the Worker are heavyweight model tier classes, the former requiring the
asp.net context to be instantiated, the latter requiring such an instance to
do the real work required to create the web page. The current method call
layout is the result of performance optimizing by minimizing the number of
cross-COM+-boundary method calls which just are too expensive in inner
loops: the Worker class (causing thousands of queries to the Cache class)
has been moved from the aspnet_wp process to the dllhost. The "evil" Cache
member in ObjCrosser of course is not used within the Calculate() call, it
just happens accidentally to be there (in fact, the member is overwritten
with one instantiated within the Worker), but needed in the snipped away
code before and after. By the way: Exchanging the order of
instantiate/dispose of the two ServicedComponents doesn't change the
described behaviour.

Of course the actual leak is trivial to elimiate (after spending endless
hours on debugging): I just have to add the line
ObjCrosser.ObjCache = Nothing
between steps 3 and 4. But my main problem is that

1) Pooled objects are by design a limited resource (a *hard* limit!).
2) Try/Finally is therefore required to guarantee that they get disposed in
*any* (yes, *any*!) case.
3) But a successfull call to .Dispose() *demonstrably* does *not* dispose
the object in all cases.

So I have two questions:

A) Is the described behaviour by design and documented somewhere, or is that
a (known?) bug?
B) Are there other known cases where ServicedComponent.Dispose() actually
doesn't to the job it is *absolutely required* to do?


Reply With Quote
  #3  
Old   
Toni Arnold
 
Posts: n/a

Default Re: ServicedComponent.Dispose(): instance not returned to pool if passed to a server app beforehand - 11-01-2006 , 08:11 AM



Quote:
If setting the variable to null releases it back to the pool, then the
dispose method is not calling Marshal.ReleaseCOMObject on itself like it
does for proxies. Try adding the call to Marshal.ReleaseCOMObject right
after you call dispose.
Before and after calling .Dispose(), a pooled ServicedComponent is equally
instantiated on the client side as a
{System.Runtime.Remoting.Proxies.__TransparentProx y}. According to the
Marshal.ReleaseComObject documentation [1], it throws a "Specified cast is
not valid." exception when trying to manually maintain* the reference
counter for a pooled ServicedComponent, as a TransparentProxy is not a
reference to an intermediate COM object itself, which seems to be required
by the Marshal.ReleaseComObject(ByVal o As Object).
*A note in [1] says "To ensure that the runtime callable wrapper and the
original COM object are released, construct a loop from which you call this
method until the returned reference count reaches zero." At least nobody
asks me to construct a trivial assembler program to ensure that the MMU
releases the individual memory pages used by the COM+ machinery ;-)

The Marshal Class documentation [2] describes it as a tool to handle the
conversion between managed and unmanaged resources. I'm aware of the fact
that the COM+ machinery is not written in .NET and therefore using a
ServicedComponent involves execution of unmanaged C++ operating system
code - but I thought that a) the .NET ServicedComponent tools should
transparently hide that fact from the application programmer (using only
managed .NET code) and b) that the .Dispose() method is exactly the tool we
need here: It is documented in [3] as "Releases all resources used by the
ServicedComponent". And this usually works regardless of the reference
counter within the client CLR - except in my case.

In my pre-.NET-COM+ book [4], pg. 258, I found this interesting, but a
little vague statement: "A proxy could also be created when an interface
pointer is passed from one context to another via some interface method
call. Once again, COM+ will take care of creating the proxy." In my case, I
think I was passing a pointer to a proxy instance, not to an IDL interface.
In any case, COM+ seems unaskedly to take care of incrementing the reference
counter, but not of decrementing it again.
Of course, as described on the same page, it makes no sense to pass that
pointer to a proxy e.g. from the aspnet_wp context to the dllhost context,
which is what I unintentionally was doing before setting the variable to
null, as "a proxy is geared to swim in a specific sea", and it does not work
outside of the context it was instantiated.

By the way: There was a mistake in my fist posting: an idle shutdown event
never happens, as the stuck ServicedComponent counts as activated, therefore
the situation was even worse than I thought.

So perhaps somebody has a good ServicedComponent COM+ book at hand and could
send me a pointer to that mystic chapter where my problem is described in
detail?

Thanks!

[1]
..NET Framework Class Library
Marshal.ReleaseComObject Method [Visual Basic]

[2]
.NET Framework Class Library
Marshal Class [Visual Basic]

[3]
NET Framework Class Library
ServicedComponent.Dispose Method [Visual Basic]

[4]
Pradeep Tapadiya: COM+ Programming. Prentice Hall, 2001





Reply With Quote
Reply




Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off



Powered by vBulletin Version 3.5.4
Copyright ©2000 - 2008, Jelsoft Enterprises Ltd.