![]() | |
![]() |
| | Thread Tools | Search this Thread | Display Modes |
#1
| |||
| |||
|
#2
| |||
| |||
|
|
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? |
#3
| |||
| |||
|
|
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. |
![]() |
| Thread Tools | Search this Thread |
| Display Modes | |
| |