.NETGURU
Bug in ServicedComponent.Dispose?
Messages   Related Types
This message was discovered on microsoft.public.dotnet.framework.component_services.
Responses highlighted in red are from those people who are likely to be able to contribute good, authoratitive information to this discussion. They include Microsoft employees, MVP's and others who IMHO contribute well to these kinds of discussions.
Post a new message to this list...

Will (VIP)
I'm having a problem with a Serviced Component Try Finally Dispose pattern.

When running in a server application, disposing the object after it has called SetAbort throws:

[COMException (0x8004e003): You made a method call on a COM+ component that has a transaction that has already aborted or in the process of aborting.]

I understand that when the object calls SetAbort, it is indicating that it is done and COM+ will deactivate the object.

However, an IDisposible component should be disposed by the client that created it. That's just a good practice, right?

This problem only occurs if the object is in a server application and it is not the object that started the transaction (I think).

To reproduce the problem, create three projects.

One class library project containing this code (library must have a strong name):

<EnterpriseServices.Transaction(EnterpriseServices.TransactionOption.Required, Timeout:=0)> _
Public Class ServicedComponentTestObject2
Inherits EnterpriseServices.ServicedComponent

Public Function Test() As Boolean

Dim result As Boolean

Try

If MsgBox("Throw exception in ServicedComponentTestObject2?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
Throw New Exception("Exception from ServicedComponentTestObject2")
Else
EnterpriseServices.ContextUtil.SetComplete()
result = True
End If

Catch ex As Exception
EnterpriseServices.ContextUtil.SetAbort()
Throw ex
End Try

Return result

End Function

End Class

Another class library project containing this code (library must have a strong name and a reference to the previous library):

<EnterpriseServices.Transaction(EnterpriseServices.TransactionOption.Required, Timeout:=0)> _
Public Class ServicedComponentTestObject
Inherits EnterpriseServices.ServicedComponent

Public Function Test() As Boolean

Dim result As Boolean

Try

Dim test2 As ServicedComponentTestObject2
test2 = New ServicedComponentTestObject2

Try
result = test2.Test()
If result Then
If MsgBox("Throw exception in ServicedComponentTestObject1?", MsgBoxStyle.YesNo) = MsgBoxResult.Yes Then
Throw New Exception("Exception from ServicedComponentTestObject")
Else
result = True
End If
End If
Finally
Try
EnterpriseServices.ServicedComponent.DisposeObject(test2)
Catch ex As Exception
MsgBox(String.Concat("Failed to dispose of ServicedComponentTestObject2 object: ", vbCrLf, vbCrLf, ex.ToString))
End Try
End Try

Catch ex As Exception
EnterpriseServices.ContextUtil.SetAbort()
Throw ex
End Try

Return result

End Function

End Class

And a command-line utility containing this code and referencing the library above:

Module ServicedComponentTestApp

Sub Main()

Try

Dim test As ServicedComponentTest.ServicedComponentTestObject

test = New ServicedComponentTest.ServicedComponentTestObject

Try
If test.Test() Then
MsgBox("Success")
End If
Finally
Try
EnterpriseServices.ServicedComponent.DisposeObject(test)
Catch ex As Exception
MsgBox(String.Concat("Failed to dispose of ServicedComponentTestObject object: ", vbCrLf, vbCrLf, ex.ToString))
End Try
End Try

Catch ex As Exception
MsgBox(ex.ToString)
End Try

End Sub

End Module

Run the command line app and the two class libraries will register themselves in two COM+ packages. When prompted click Yes to the "Throw exception in ServicedComponentTestObject2" prompt. Note that the exception is returned to ServicedComponentTestObject and ServicedComponentTestObject successfully disposes of the ServicedComponentTestObject2 object. Now change the package containing ServicedComponentTestObject2 to a service application (NOTE you will probably need to disable access checks) and rerun the app. Now when you answer yes to the "Throw exception in ServicedComponentTestObject2" prompt and ServicedComponentTestObject tries to dispose of ServicedComponentTestObject2, the DisposeObject method throws:

[COMException (0x8004e003): You made a method call on a COM+ component that has a transaction that has already aborted or in the process of aborting.]

Note that there is not difference in behavior between DisposeObject and Dispose.

While the error is technically correct, shouldn't the client be able to dispose of the object? Using a try finally dispose pattern for an IDisposible object seems to be good programming but depending on how the component is served, the dispose may or may not work. This isn't good encapsulation because the client must know how the component is and whether it called SetAbort.

Is this a bug or am I missing something?

Reply to this message...
 
    
Stan
It is not a bug, it is by design. Wrap you dispose in try-cach

try { o.Dispose(); } catch {}

"Will" <Click here to reveal e-mail address> wrote in message
news:Click here to reveal e-mail address...
> I'm having a problem with a Serviced Component Try Finally Dispose
pattern.
[Original message clipped]

aborting.]
[Original message clipped]

d, Timeout:=0)> _
[Original message clipped]

d, Timeout:=0)> _
[Original message clipped]

exception in ServicedComponentTestObject2" prompt. Note that the exception
is returned to ServicedComponentTestObject and ServicedComponentTestObject
successfully disposes of the ServicedComponentTestObject2 object. Now
change the package containing ServicedComponentTestObject2 to a service
application (NOTE you will probably need to disable access checks) and rerun
the app. Now when you answer yes to the "Throw exception in
ServicedComponentTestObject2" prompt and ServicedComponentTestObject tries
to dispose of ServicedComponentTestObject2, the DisposeObject method throws:
[Original message clipped]

aborting.]
[Original message clipped]

IDisposible object seems to be good programming but depending on how the
component is served, the dispose may or may not work. This isn't good
encapsulation because the client must know how the component is and whether
it called SetAbort.
[Original message clipped]

Reply to this message...
 
    
Will (VIP)
If this is by design then it is a poor design.

If dispose throws an error, how do I know the object was successfully disposed? Why would the design stipulate that in one case, dispose succeeds and in another it fails?

The dispose method in ServicedComponent should simply teardown the proxy if the real object has been deactivated by COM+. Otherwise, the disposal of the ServicedComponent should release the proxy which should decrement the reference to the real object causing it to be released.

Note that SetComplete also implies that COM+ should deactivate the object but calling dispose in this case never throws an error.

Disposing of the object should never be related to the transactional outcome.

"Stan" wrote:

[Original message clipped]

Reply to this message...
 
    
Stan
This only happens when
a) you dispose a none-root serviced component
b) component aborts the transaction

By design COM+ does not allow any calls into aborted context and when you
call Dispose this rule is broken and COM+ throws CONTEXT_E_ABORTING

There is nothing you can do about it other then wrapping the secondary
component in try-catch...

"Will" <Click here to reveal e-mail address> wrote in message
news:Click here to reveal e-mail address...
[Original message clipped]

succeeds and in another it fails?
[Original message clipped]

of the ServicedComponent should release the proxy which should decrement the
reference to the real object causing it to be released.
[Original message clipped]

Reply to this message...
 
    
Will (VIP)
Stan,

Thanks for the insight and feedback. I thought that was the case. Still seems that the .NET Framework should work around this or consume the error itself.

Do you know if, under these specific conditions, the .NET related resources will be freed correctly even though the exception is thrown?

Will

"Stan" wrote:

[Original message clipped]

Reply to this message...
 
    
Stan
> Do you know if, under these specific conditions, the .NET related
resources will be freed correctly even though the exception is thrown?

Garbage collector by design does not dispose the serviced components. MS
took this functionality out when they found that GC dispose takes too much
resources. So, always dispose yourself

Reply to this message...
 
    
Ramanujam Sampath
(Type your message here)

--------------------------------
From: Ramanujam Sampath

I have a similar problem. I have copied the method below from where the same problem occurs. Do let me know sa what can be done for this. Where do i implement your action.

        private void ScrubChangedAddress(string szConnection, string szConnectionCRM)
        {
            //Create a Transaction worker class
            TransactionWorker oWorker = null;
            oWorker = new TransactionWorker();
            try
            {
                //Get The Last Run Date from Control Table (Date From)
                DateTime dtDCtlPrv = Convert.ToDateTime(GetLastRunDateTime(szConnection));
                //Set the current Date (Date To)
                DateTime dtDCtlCur = DateTime.Now;
                
                //Access Checkpoint to check the restart
                Schwans.Services.CheckPoint oCheckpoint = new Schwans.Services.CheckPoint();
                int iOCus = 0, iOCusAdr = 0;
                string szRestart1, szRestart2, szRestart3;

                //Fetch the Checkpoint Record
                if (true == oCheckpoint.Find(szConnectionCRM ,GetAppName(), RR157.CONST_COMPANY, RR157.RUN_OPT, out szRestart1, out szRestart2, out szRestart3))
                {
                    //Last processed Customer Number    
                    iOCus = Convert.ToInt32(szRestart1.Trim());
                    //Last processed Customer Address Number    
                    iOCusAdr = Convert.ToInt32(szRestart2.Trim());
                    //Last processing Date Time
                    dtDCtlCur = Convert.ToDateTime(szRestart3.Trim());
                    LogMessage("Re Start found." + szRestart1 + " " + szRestart2 + " " + szRestart3);
                }

                //Get the list of Customer changes from the Audit Table
                DataTable dtbAddress= new DataTable("CTV38000_CHANGES");
                SSESqlConnection oSqlConnection = new SSESqlConnection(szConnection);
                oSqlConnection.CommandTimeout = 300;
                oSqlConnection.Open();

                LogMessage("Querying CTV38000 for Changes");
                oSqlConnection.Parameters.Clear();
                //Last Run From Date Time from Control
                oSqlConnection.Parameters.AddDateTime("@in_DRecChangeBgn",dtDCtlPrv);    
                //Last Run To Date (Current or from Checkpoint)
                oSqlConnection.Parameters.AddDateTime("@in_DRecChangeEnd",dtDCtlCur);    
                //Last Processed customer
                oSqlConnection.Parameters.AddInt("@in_OCus",iOCus);
                //Last Processed customer Address Number
                oSqlConnection.Parameters.AddInt("@in_OCusAdr",0);
                //Execute the Address Change Query
                oSqlConnection.ExecuteQuerySP("RR157_SelectAddressChangesForScrub",dtbAddress);
                LogMessage(dtbAddress.Rows.Count.ToString() + " Changed Records found...");
                //Destroy the connection
                oSqlConnection.Close();
                oSqlConnection = null;

                int iTransactionCommit = this.RegistryLocalMachine.ReadInt("CommitCount", 100);

                //Perform Processing transactionally
                int MAX_RECORDS_PER_TRANSACTION = iTransactionCommit;
                int iRecordsProcessed = 0;

                //For each customer change on Legacy
                foreach(DataRow drAddress in dtbAddress.Rows)
                {
                    //Extract the Keys (O_CUS, O_CUS_ADR)
                    iOCus = Convert.ToInt32(drAddress["O_CUS"].ToString());
                    iOCusAdr = Convert.ToInt32(drAddress["O_CUS_ADR"].ToString());

                    //Standardize Customer address
                    oWorker.ProcessCustomerAddress(iOCus, iOCusAdr, dtDCtlCur);
                    //Increament the counter
                    iRecordsProcessed++;
                    //Check if time to commit
                    if (iRecordsProcessed > MAX_RECORDS_PER_TRANSACTION)
                    {
                        //Save the Checkpoint
                        oWorker.SaveCheckpoint(GetAppName(), iOCus.ToString(), iOCusAdr.ToString(), dtDCtlCur.ToString());
                        //Commit the Transaction
                        oWorker.CommitTransaction();
                        LogMessage("Checkpoint Save and Commit");

#region Log#2524840
//Create New Object
LogMessage("Before code change : " + iRecordsProcessed.ToString());
oWorker = null;
LogMessage("After Code Change : " + iRecordsProcessed.ToString());
#endregion

                        oWorker = new TransactionWorker();
                        //Reset the counter
                        iRecordsProcessed = 0;
                    }
                }
                //Clean-up
                oWorker.DeleteCheckpoint(GetAppName());
                LogMessage("Delete Checkpoint");
                //Update the control table for next Run
                oWorker.UpdateControlRecord(szConnection, dtDCtlCur.ToString());
                //Commit the Transaction
                oWorker.CommitTransaction();
                LogMessage("Update control record and Commit");
            }
            catch (Exception oErr)
            {
                //Always Abort when error occurs
                oWorker.AbortTransaction();
                throw oErr;
            }
        }

Thanks
Ramanujam
Reply to this message...
 
    
Ed (VIP)
So when a transaction aborts you should not call Dispose() for non-root
component (for example, from other, possible root component)? But here is a
question: what will happen with non root component which participates in
aborted transaction if no Dispose() call is made? What happens with its
context? Is there a danger that in multithread environment environment
KB327443 bug may occur?
Reply to this message...
 
 
System.Convert
System.Data.DataRow
System.Data.DataTable
System.DateTime
System.EnterpriseServices.ContextUtil
System.EnterpriseServices.ServicedComponent
System.EnterpriseServices.TransactionOption
System.Runtime.InteropServices.COMException
System.String




ExamGuru IT Solutions - .Net Guru is owned and operated by ExamGuru, Inc., the man behind .Net Guru. If you're in the market for bespoke software or software consultancy, why not get him and his highly trained team to help? - www.examguru.net/ITCertification
Ad


Need Dot Net Interview Questions?
Ask ExamGuru, Inc. for advice and help on Passing .Net Interviews
.Net Projects
Best-of-breed application framework for .NET projects, developed by ExamGuru, Inc. and ExamGuru IT
Free .net Help
Commission ExamGuru, Inc. and his team for your next bespoke software project
FogBUGZ
The only bug tracking system carefully crafted with one goal in mind: helping teams create great software.
Awesome Tools
If you don't know about these, you're missing out... IT Certification Questions
IT Interview Questions
Free Oracle 10g Training
MCSE Boortcamp
Cisco Study Guides
Cheap Study Guides
Exact Questions
Dot Net Interview Questions
Oracle OCP
Cheap Travel
Designer Perfumes - Wholesale Prices
Free Programming Tutorials
 
ExamGuru IT Solutions - .Net Guru is owned and operated by ExamGuru, Inc., the man behind .Net Guru. If you're in the market for bespoke software or software consultancy, why not get him and his highly trained team to help? - www.examguru.net/ITCertification
 Copyright © ExamGuru, Inc. 2001-2006
Contact Us - Terms of Use - Privacy Policy - www.dot-net-guru.com - www.examguru.net - www.oraclesource.net - www.itinterviews.net - www.examguru.net/ITCertification