41

I have a long running TransactionScope in C#. I told the scope that it should have a long timespan, but still I get a timeout. What could cause this?

TransactionOptions transactionOptions = new TransactionOptions();
transactionOptions.IsolationLevel = IsolationLevel.ReadCommitted;
transactionOptions.Timeout = TimeSpan.MaxValue;
using (var ts = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
{ 
    DoLongCode();
}
Jim Aho
  • 9,932
  • 15
  • 56
  • 87
Patrick
  • 2,730
  • 4
  • 33
  • 55
  • 1
    As @nonnb mentions, you'll need to set the timeout on the SQL calls, or the object context as well. – Grant H. Aug 21 '12 at 13:09
  • 7
    Ouch; **why** would you want a 10-minute `TransactionScope`? I would be getting *extremely* anxious if I had a `TransactionScope` that lasted more than a couple of seconds. Long-running transactions can severely impact all other callers... You also need to consider the commit/rollback cost; on many platforms, it is "rollback" that pays a penalty (commit being cheap); if this has done a lot of work in the 10 minutes, the rollback could be a **killer**. – Marc Gravell Aug 21 '12 at 13:13
  • It is indeed not a desired situation but I have to deal with it atm. It is a weakly process that has a terrible performance. – Patrick Aug 21 '12 at 13:17
  • @MarcGravell Because sometimes you just have to move a lot of data. – Suncat2000 Feb 02 '21 at 16:55

9 Answers9

43

To further clarify:

Transaction Scope uses the Machine config setting as the maximum timeout. The default machine timeout is 10 minutes.

Setting the machine config to 2 hours:

      <system.transactions>
        <machineSettings maxTimeout="02:00:00"/>
      </system.transactions> 

The app.config or web.config can be used reduced to the timeout but can not be used to exceed the machine config timeout.

Setting the app config to 1 hour:

<system.transactions>
     <defaultSettings timeout="01:00:00" />
</system.transactions>

Also we did NOT receive any exceptions when the limit was reached, also no trace or event log records.

Also the TransactionScope object has constructor overloads which allow you to specify a timeout, but I'm not sure how that is handled.

43

To allow transaction to take more than 10 minutes, without need to change machine.config, use this code

    private void SetTransactionManagerField(string fieldName, object value)
    {
        typeof(TransactionManager).GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static).SetValue(null, value);
    }

    public TransactionScope CreateTransactionScope(TimeSpan timeout)
    {
        // or for netcore / .net5+ use these names instead:
        //    s_cachedMaxTimeout
        //    s_maximumTimeout
        SetTransactionManagerField("_cachedMaxTimeout", true);
        SetTransactionManagerField("_maximumTimeout", timeout);
        return new TransactionScope(TransactionScopeOption.RequiresNew, timeout);
    }

Usage:

using (var ts = CreateTransactionScope(TimeSpan.FromMinutes(20)))
{ 
    DoLongCode();
    ts.Complete();
}

Based on this article The code of article was originally pasted here. The code in the answer is now refactored and simplified.

Rory
  • 40,559
  • 52
  • 175
  • 261
Jupaol
  • 21,107
  • 8
  • 68
  • 100
  • Not how I wished to implement it, but It's a solution and works for me (Azure WebJob) – martinoss Sep 26 '19 at 07:18
  • I like this solution a lot as in many environments you have no access to the machine.config and this solution does work as intended. – Josh Dec 19 '19 at 20:34
  • 2
    For me, the fields were named s_cachedMaxTimeout and s_maximumTimeout, so I've changed it to try either field name and use what works. This is still pretty much a hack (as noted in the original article), but sometimes there really is no other option. – Alan Fluka Jul 15 '20 at 08:32
  • Which perfectly demonstrates the problem with this hack. It breaks easily. This should never be used in production code. Who knows, in a next version these members may be completely gone and you have a big problem. – Gert Arnold Nov 05 '20 at 08:39
26

Hello you can verify maxTimeout in your config file, if you don't have this section on your web.config or app.config

Verify your machine.config

<configuration> 
  <system.transactions>
    <machineSettings maxTimeout=""/>
  </system.transactions>
</configuration> 

Adjust the value

Aghilas Yakoub
  • 28,516
  • 5
  • 46
  • 51
  • 3
    In Windows Azure this cannot be changed, 10 minutes is the maximum timeout. – schglurps Aug 21 '12 at 13:35
  • 1
    Yes it's possible to override machine.config – Aghilas Yakoub Aug 21 '12 at 15:24
  • 4
    According to this thread on MSDN you cannot override the maxTimeout value in your web.config or app.config file. http://social.msdn.microsoft.com/Forums/da-DK/windowstransactionsprogramming/thread/584b8e81-f375-4c76-8cf0-a5310455a394 – jpierson Sep 18 '12 at 14:28
  • 7
    @jpierson I really hate Microsoft for this. Why should I change a system setting when there is just this single app which needs extraordinary behavior. Have they ever thought about exclusive mode local DBs like 'LocalDB', 'SQLCe', 'SQLite'? NO! – springy76 Jul 17 '14 at 07:59
7

They dont' work because is the wrong context where you are trying to change timeout.

try to change it closer to the effective query.

You should have these contexts:

    using (var txn = new TransactionScope(
                            TransactionScopeOption.Required,
                            new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted, Timeout = new TimeSpan(1,0,0) })) // 1 hour or wathever, will not affect anything
                    {

                        using (SqlConnection connection = new SqlConnection(ConnectionString))
                        {
                            int ct = connection.ConnectionTimeout // (read Only, this is the effective default timeout is 15 seconds)
                            connection.Open();

                            SqlCommand select = new SqlCommand(sql.query, connection); // bind to server
                            select.CommandTimeout = 0; // <-- here does apply infinite timeout
SqlDataReader reader = select.ExecuteReader(); // never stop
Fabio Guerrazzi
  • 164
  • 1
  • 5
4

I resolve this problem modifying the "physical file" machine.config.


1. You have to localize the file:

  • 32 Bits: C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config\machie.config
  • 64 Bits: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config

2. You have to add the following code:

<system.transactions>
     <defaultSettings timeout="00:59:00" />
</system.transactions>
3

Considering a full trust environment, you can override max timeout using reflection:

            //Get machineSettings session
            var machineSettings = (System.Transactions.Configuration.MachineSettingsSection)ConfigurationManager.GetSection("system.transactions/machineSettings");
            //Allow modifications
            var bReadOnly = (typeof(ConfigurationElement)).GetField("_bReadOnly", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
            bReadOnly.SetValue(machineSettings, false);
            //Change max allowed timeout
            machineSettings.MaxTimeout = TimeSpan.MaxValue;

            using (var t = new TransactionScope(TransactionScopeOption.Required, new TimeSpan(1,0,0))) { //1 hour transaction
                //...
            }
  • Possible limitation: it should be called before any transaction (works fine in console, but not in web app). – Maxim Nov 30 '17 at 00:45
1

If anyone is wondering how to achieve this on .NET Core, this is the answer. Basically, you have to update the fields' names:

_cachedMaxTimeout -> s_cachedMaxTimeout

_maximumTimeout -> s_maximumTimeout

Rory
  • 40,559
  • 52
  • 175
  • 261
wodzu
  • 3,004
  • 3
  • 25
  • 41
  • Not sure at what point it was changed, but current Core library has a normal setter for it. System.Transactions.TransactionManager.MaximumTimeout = ...; System.Transactions.TransactionManager.DefaultTimeout = ...; – Cine Jan 07 '23 at 10:30
0

You can add this code in your project to extend the transaction time.

// This is used for set the transaction timeout to 40 minutes.
Type oSystemType = typeof(global::System.Transactions.TransactionManager);
System.Reflection.FieldInfo oCachedMaxTimeout = 
                    oSystemType.GetField("_cachedMaxTimeout", 
                    System.Reflection.BindingFlags.NonPublic | 
                    System.Reflection.BindingFlags.Static);
System.Reflection.FieldInfo oMaximumTimeout = 
                    oSystemType.GetField("_maximumTimeout", 
                    System.Reflection.BindingFlags.NonPublic | 
                    System.Reflection.BindingFlags.Static);
oCachedMaxTimeout.SetValue(null, true);
oMaximumTimeout.SetValue(null, TimeSpan.FromSeconds(2400));
Mantu
  • 29
  • 4
  • Please... reflection should never be used to set inaccessible members because, basically, you don't know what you're doing. There are legal method to do what OP wants. Also, this is a repetition of an existing answer. – Gert Arnold Nov 05 '20 at 08:33
  • @GertArnold I would generally agree that setting the value of a private member of a type that you do not own is a very bad idea. However I do not agree for this specific scenario. AFAIK the only officially supported way to increase the maximum for the transaction timeout is modifying the machine.config. However this is something we are all to often not allowed to do, because of the IT policies that are in place. Almost none of my customers would allow anyone to make such a change. Therefore using the mentioned hack is AFAIK the only possible alternative solution to this problem. – David Liebeherr Jun 02 '21 at 01:36
0

TransactionScope has multiple constructors. Some of them allow you to change Timeout without config changes.

using (var scope = new TransactionScope(new TransactionScopeOption(), TimeSpan.FromMinutes(3), TransactionScopeAsyncFlowOption.Enabled))
{
    // Long operation here

    scope.Complete();
}
Buzzy
  • 1,783
  • 18
  • 15