Click here to Skip to main content
15,890,741 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I do not understand, how the transaction timeout works in COM+. I have the class ClassComPlus which inherits from ServicedComponent, has transaction timeout attribute set to 1 second, and has a method SubSleep, that sleeps 3 seconds. I expect, the client which calls the method SubSleep to get an exception, because the transaction timeout has elapsed, but I do not get such an exception, the method completes without an exception.

What I have tried:

Here is the code in VB.NET:

1. Project Test1BO.vbproj as Class Library signed with a strong key, having the two files:

1.1 AssemblyInfo.vb:

Imports System.EnterpriseServices
Imports System.Reflection
Imports System.Runtime.InteropServices
<assembly: applicationactivation(activationoption.server)>
<assembly: applicationaccesscontrol(ctype(accesschecksleveloption.application, boolean))>
<assembly: guid("799facfd-af56-4496-bc18-618e2522e5f7")>
<assembly: assemblyversion("1.0.0.0")>
<assembly: assemblyfileversion("1.0.0.0")>


1.2 ClassComPlus.vb

Imports System.EnterpriseServices

    <transaction(transactionoption.required, isolation:="TransactionIsolationLevel.ReadCommitted", timeout:="1"), _ eventtrackingenabled(true), justintimeactivation(true)> _
    Public Class ClassComPlus
        Inherits ServicedComponent

        Public Sub SubSleep()
            Try
                Threading.Thread.Sleep(3000)
                ContextUtil.SetComplete()
            Catch
                ContextUtil.SetAbort()
                Throw
            End Try
        End Sub
    End Class

Test1BO must be registered in COM+ for example with the following RegisterComPlus.bat file in the folder where the dll is created:
set regsvcs=C:\Windows\Microsoft.NET\Framework\v4.0.30319\regsvcs
set topdir=%~dp0
set dllname=Test1BO
%regsvcs% /u "%topdir%\%dllname%.dll"
%regsvcs% "%topdir%\%dllname%.dll"
pause


2. Project Test1CA.vbproj as Console Application with reference to Test1BO.dll has just Module1.vb:

Module Module1
        Sub Main()
            Dim obj = New Test1BO.ClassComPlus
            Try
                obj.SubSleep()
                Console.WriteLine("SubSleep finished normally (unexpected)")
            Catch ex As Exception
                Console.WriteLine("SubSleep threw exception (expected)")
                Console.WriteLine(ex.ToString)
            Finally
                obj.Dispose()
            End Try
            Console.ReadKey()
        End Sub
    End Module

I would expect, that because the transaction timeout is 1 second, but the sleep time is 3 seconds, that the statement obj.SubSleep would throw an exception. Instead, obj.SubSleep finishes normally.

1. What am I doing wrong?
2. Did I wrongly understood the transaction timeout?
Posted
Updated 12-Dec-16 3:51am
v6

1 solution

With the help of Microsoft, I finally understood how the COM+ transaction timeout works. The COM+ transaction timeout, is a timeout on the transaction, not on the method call. The method will execute, as long as it has to execute. The moment the first transaction is enlisted, the transaction timeout starts to work. If the transaction takes more time than the specified timeout, the transaction is marked for abortion, but is not canceled, it continues to run. When the COM+ method calls SetComplete, only then an exception is thrown. The timeout exception is the following:

System.Runtime.InteropServices.ComException: {"The root transaction wanted to commit, but transaction aborted (Exception from HRESULT: 0x8004E002)"}.

A transaction is enlisted as soon as a connection is opened to the database or as soon as something is written to a message queue.

Suppose the transaction timeout is 10 seconds. Let us consider the following 3 cases:

1. The COM+ method immediately enlists a transaction that takes 11 seconds.
In this case the timeout exception is thrown after 11 seconds, because the transaction took longer than the timeout.
2. The COM+ method sleeps for 6 seconds (or does some calculations) and then enlists a transaction that takes 5 seconds.
In this case the method completes without an error, although the total execution time is also 11 seconds.
3. The COM+ method immediately enlists a transaction that takes 5 seconds, and then sleeps for 6 seconds.
In this case the timeout exception is thrown after 11 seconds, because the time between the enlistment of the transaction and the SetComplete call is 11 seconds, which is more than the timeout.

In summary: If the time between the first enlistment of the transaction and the SetComplete call is more than the transaction timeout, the timeout exception is thrown. However, the COM+ method always runs, for as long as it has to run, irrespective to the transaction timeout.

To test the above cases, here is the code in VB.NET:

1. Project Test1BO.vbproj as Class Library signed with a strong key, having the two files:

1.1 AssemblyInfo.vb:
Imports System.EnterpriseServices
Imports System.Reflection
Imports System.Runtime.InteropServices
<Assembly: ApplicationActivation(ActivationOption.Server)>
<Assembly: ApplicationAccessControl(CType(AccessChecksLevelOption.Application, Boolean))>
<Assembly: Guid("799facfd-af56-4496-bc18-618e2522e5f7")>
<Assembly: AssemblyVersion("1.0.0.0")>
<Assembly: AssemblyFileVersion("1.0.0.0")>

1.2 ClassComPlus.vb
Imports System.EnterpriseServices
Imports System.Data.SqlClient
Imports System.Transactions

<Transaction(TransactionOption.Required, isolation:=TransactionIsolationLevel.Serializable, timeout:=10), _
 EventTrackingEnabled(True), _
 JustInTimeActivation(True)>
Public Class ClassComPlus
    Inherits ServicedComponent

    Public Function DbExecuteNonQuery(
        connectionString As String, cmdText As String,
        sleepSecondsBefore As Integer, sleepSecondsAfter As Integer) As Integer
        Try
            Threading.Thread.Sleep(sleepSecondsBefore * 1000)
            Dim result = 0
            Using cn = New SqlConnection(connectionString)
                cn.Open()
                Dim cmd = New SqlCommand(cmdText, cn)
                result = cmd.ExecuteNonQuery()
            End Using
            Threading.Thread.Sleep(sleepSecondsAfter * 1000)
            ContextUtil.SetComplete()
            Return result
        Catch
            ContextUtil.SetAbort()
            Throw
        End Try
    End Function

Test1BO must be registered in COM+ for example with the following RegisterComPlus.bat file in the folder where the dll is created:
set regsvcs=C:\Windows\Microsoft.NET\Framework\v4.0.30319\regsvcs
set topdir=%~dp0
set dllname=Test1BO
%regsvcs% /u "%topdir%\%dllname%.dll"
%regsvcs% "%topdir%\%dllname%.dll"
pause


2. Project Test1CA.vbproj as Console Application with reference to Test1BO.dll has just Module1.vb:
Module Module1
Private Sub MyTrace(s As String)
    Console.WriteLine(String.Format("{0} {1}", Now.ToString("yyyy-MM-dd HH:mm:ss"), s))
End Sub

Private Sub TestDbExecNonQuery()
    Dim obj = New Test1BO.ClassComPlus
    Dim connectionString = ConfigurationManager.ConnectionStrings("DB1").ConnectionString
    Dim cmdText = "WAITFOR DELAY '00:00:011'"
    Try
        MyTrace("TestDbExecNonQuery begin " & cmdText & ", 0, 0")
        obj.DbExecuteNonQuery(connectionString, cmdText, 0, 0)
        MyTrace("TestDbExecNonQuery end")
    Catch ex As Exception
        MyTrace("TestDbExecNonQuery exception")
        MyTrace(ex.Message)
    End Try
    cmdText = "WAITFOR DELAY '00:00:05'"
    Try
        MyTrace("TestDbExecNonQuery begin " & cmdText & ", 6, 0")
        obj.DbExecuteNonQuery(connectionString, cmdText, 6, 0)
        MyTrace("TestDbExecNonQuery end")
    Catch ex As Exception
        MyTrace("TestDbExecNonQuery exception")
        MyTrace(ex.Message)
    End Try
    Try
        MyTrace("TestDbExecNonQuery begin " & cmdText & ", 0, 6")
        obj.DbExecuteNonQuery(connectionString, cmdText, 0, 6)
        MyTrace("TestDbExecNonQuery end")
    Catch ex As Exception
        MyTrace("TestDbExecNonQuery exception")
        MyTrace(ex.Message)
    End Try
    obj.Dispose()
End Sub

Sub Main()
    TestDbExecNonQuery()
    Console.WriteLine("Press any key to close")
    Console.ReadKey()
End Sub
End Module

Running the above, the following output is expected:

2016-12-09 16:47:17 TestDbExecNonQuery begin WAITFOR DELAY '00:00:011', 0, 0
2016-12-09 16:47:29 TestDbExecNonQuery exception
2016-12-09 16:47:29 The root transaction wanted to commit, but transaction aborted (Exception from HRESULT: 0x8004E002)
2016-12-09 16:47:29 TestDbExecNonQuery begin WAITFOR DELAY '00:00:05', 6, 0
2016-12-09 16:47:40 TestDbExecNonQuery end
2016-12-09 16:47:40 TestDbExecNonQuery begin WAITFOR DELAY '00:00:05', 0, 6
2016-12-09 16:47:51 TestDbExecNonQuery exception
2016-12-09 16:47:51 The root transaction wanted to commit, but transaction aborted (Exception from HRESULT: 0x8004E002)
Press any key to close
 
Share this answer
 
Comments
F-ES Sitecore 12-Dec-16 10:08am    
"The COM+ transaction timeout, is a timeout on the transaction"

Hoodafunk? :)
Patrice T 12-Dec-16 19:46pm    
Accept your solution to close the question.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900