When using the System.Transactions.TransactionScope there is a default machine setting timeout of 10 minutes. If you have a long running transaction and it runs for over 10 minutes, it will timeout with the following exception message:
"The operation
is not valid for the state of the transaction."
And an inner
exception message of:
"Transaction
Timeout"
There appear to be three ways to deal with this:
1.) Setting the
system.transactions machinesettings maxtimeout in the machine.config on the
server. This is done by adding the
following to the machine.config:
<system.transactions>
<machineSettings
maxTimeout="00:20:00" />
</system.transactions>
This is a machine
wide setting and it may interfere with administrative policies. It directly effects all applications running
on the server.
2.) Setting
allowExeDefinition="MachineToApplication" on the system.transaction
section group in the machine.config. The
default is "MachineOnly". See
below for machine.config configuration:
<sectionGroup
name="system.transactions"
type="System.Transactions.Configuration.TransactionsSectionGroup,
System.Transactions, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089, Custom=null">
<section
name="defaultSettings"
type="System.Transactions.Configuration.DefaultSettingsSection,
System.Transactions, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089, Custom=null"/>
<section
name="machineSettings"
type="System.Transactions.Configuration.MachineSettingsSection,
System.Transactions, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089, Custom=null"
allowDefinition="MachineOnly" allowExeDefinition="MachineToApplication"/>
</sectionGroup>
This allows one to
add the following to an application's app.config and override the machine wide
setting (the default of 10 minutes or whatever setting is defined in the
machine.config.):
<system.transactions>
<machineSettings
maxTimeout="00:20:00" />
</system.transactions>
Although this
approach changes a machine wide setting, it does not change the machine wide
maxtimeout for the machine. One will be
able to retain whatever value is set for the maxTimeout and one will be able to
set whatever value is fit for the specific application in the app.config. Thus, each app can override the machine wide
maxTimeout setting and set its own maxTimeout.
I think this is better than the first approach as it is a bit more
secure. It doesn't change the machine
wide maxTimeout setting which changing could consequently expose a DOS
situation for other apps that may be on the server. There may be a company policy that doesn't
allow this option as it would give any application the ability to change the
setting.
3.) Use reflection
to change the setting via custom code.
This approach accesses private data members of Microsoft classes and
thus could break in the future. It
doesn't require modifying the machine.config,
it doesn't open up other application to possible DOS situations and it
circumvents any company policy. Below is
the code that does this:
public static class
TransactionManagerHelper
{
public static void
OverrideMaximumTimeout(TimeSpan timeout)
{
//TransactionScope inherits a
*maximum* timeout from Machine.config.
There's no way to override it from
//code unless you use
reflection. Hence this code!
//TransactionManager._cachedMaxTimeout
var type =
typeof(TransactionManager);
var cachedMaxTimeout =
type.GetField("_cachedMaxTimeout", BindingFlags.NonPublic |
BindingFlags.Static);
cachedMaxTimeout.SetValue(null,
true);
//TransactionManager._maximumTimeout
var maximumTimeout =
type.GetField("_maximumTimeout", BindingFlags.NonPublic |
BindingFlags.Static);
maximumTimeout.SetValue(null,
timeout);
}
}
To use it call the
following before creating the TransactionScope object:
TransactionManagerHelper.OverrideMaximumTimeout(TimeSpan.FromMinutes(5));
I've tested all
three options. They will all work. I also verified that using the reflection
method will change the setting for just the one application and not for other
applications on the same server.
I
propose using the reflection method and using a custom appSetting from the
app.config to specify the timeout value passed to OverrideMaximumTimeout.
Another option would be to re-use the System.Transactions.Configuration.DefaultSettingsSection
configuration section. If a Timeout
value exists for that configuration section in the app.config then pull the
value from there and use it in the call to OverrideMaximumTimeout.
No comments:
Post a Comment