From f280ef655c549f2dfea2742282dea630a073fc34 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 14 Jul 2026 04:46:44 +0000
Subject: [PATCH 1/4] Initial plan
From 9b36fdeae92854931f26ce71ba0cd247ecdce5b9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 14 Jul 2026 05:21:03 +0000
Subject: [PATCH 2/4] Fix TransactionManager race condition in
DefaultTimeout/MaximumTimeout setters
The DefaultTimeout setter was using Interlocked.Exchange to write
s_defaultTimeoutTicks and then setting s_defaultTimeoutValidated = true
without holding a lock. This raced with the getter's
LazyInitializer.EnsureInitialized (which uses s_classSyncObject as its
lock) - a concurrent getter thread could see s_defaultTimeoutValidated
as false, enter EnsureInitialized, acquire the lock, and overwrite the
setter's new value with the config default.
Fix: wrap the writes in DefaultTimeout.set in lock(ClassSyncObject) so
they are mutually exclusive with EnsureInitialized. Keep
Interlocked.Exchange for 32-bit atomicity of the 64-bit field.
Also fix MaximumTimeout.set which had the same issue:
- Wrap writes in lock(ClassSyncObject)
- Fix wrong flag-before-value ordering (was: s_cachedMaxTimeout = true
then s_maximumTimeout = value; now: value before flag)
Fixes dotnet/runtime#105124
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
---
.../System/Transactions/TransactionManager.cs | 33 +++++++++++--------
1 file changed, 20 insertions(+), 13 deletions(-)
diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs
index b98f51e538188a..a7c21e0c5de17b 100644
--- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs
+++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs
@@ -320,8 +320,13 @@ public static TimeSpan DefaultTimeout
etwLog.MethodEnter(TraceSourceType.TraceSourceBase, "TransactionManager.set_DefaultTimeout");
}
- Interlocked.Exchange(ref s_defaultTimeoutTicks, ValidateTimeout(value).Ticks);
- if (Interlocked.Read(ref s_defaultTimeoutTicks) != value.Ticks)
+ TimeSpan validatedTimeout = ValidateTimeout(value);
+ lock (ClassSyncObject)
+ {
+ Interlocked.Exchange(ref s_defaultTimeoutTicks, validatedTimeout.Ticks);
+ s_defaultTimeoutValidated = true;
+ }
+ if (validatedTimeout != value)
{
if (etwLog.IsEnabled())
{
@@ -329,8 +334,6 @@ public static TimeSpan DefaultTimeout
}
}
- s_defaultTimeoutValidated = true;
-
if (etwLog.IsEnabled())
{
etwLog.MethodExit(TraceSourceType.TraceSourceBase, "TransactionManager.set_DefaultTimeout");
@@ -370,17 +373,21 @@ public static TimeSpan MaximumTimeout
ArgumentOutOfRangeException.ThrowIfLessThan(value, TimeSpan.Zero);
- s_cachedMaxTimeout = true;
- s_maximumTimeout = value;
- LazyInitializer.EnsureInitialized(ref s_defaultTimeoutTicks, ref s_defaultTimeoutValidated, ref s_classSyncObject, () => DefaultSettingsSection.Timeout.Ticks);
-
- long defaultTimeoutTicks = Interlocked.Read(ref s_defaultTimeoutTicks);
- Interlocked.Exchange(ref s_defaultTimeoutTicks, ValidateTimeout(new TimeSpan(defaultTimeoutTicks)).Ticks);
- if (Interlocked.Read(ref s_defaultTimeoutTicks) != defaultTimeoutTicks)
+ lock (ClassSyncObject)
{
- if (etwLog.IsEnabled())
+ s_maximumTimeout = value;
+ s_cachedMaxTimeout = true;
+ LazyInitializer.EnsureInitialized(ref s_defaultTimeoutTicks, ref s_defaultTimeoutValidated, ref s_classSyncObject, () => DefaultSettingsSection.Timeout.Ticks);
+
+ long defaultTimeoutTicks = Interlocked.Read(ref s_defaultTimeoutTicks);
+ long validatedDefaultTicks = ValidateTimeout(new TimeSpan(defaultTimeoutTicks)).Ticks;
+ if (validatedDefaultTicks != defaultTimeoutTicks)
{
- etwLog.ConfiguredDefaultTimeoutAdjusted();
+ Interlocked.Exchange(ref s_defaultTimeoutTicks, validatedDefaultTicks);
+ if (etwLog.IsEnabled())
+ {
+ etwLog.ConfiguredDefaultTimeoutAdjusted();
+ }
}
}
From e02b92b3cee265cc6ff3f9c0f91764daf245667c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 29 Jul 2026 22:18:47 +0000
Subject: [PATCH 3/4] Restore original Interlocked.Read comparison in
DefaultTimeout.set
Per reviewer feedback, keep the original comparison style
(Interlocked.Read(ref s_defaultTimeoutTicks) != value.Ticks) which
checks whether the stored timeout differs from what was requested,
rather than checking whether validation changed the value.
Move the comparison inside the lock so it reads back the value just
stored by this thread (not a concurrent thread's modification).
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
---
.../src/System/Transactions/TransactionManager.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs
index a7c21e0c5de17b..e1b8a6d97423cc 100644
--- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs
+++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs
@@ -321,12 +321,14 @@ public static TimeSpan DefaultTimeout
}
TimeSpan validatedTimeout = ValidateTimeout(value);
+ bool timeoutAdjusted;
lock (ClassSyncObject)
{
Interlocked.Exchange(ref s_defaultTimeoutTicks, validatedTimeout.Ticks);
s_defaultTimeoutValidated = true;
+ timeoutAdjusted = Interlocked.Read(ref s_defaultTimeoutTicks) != value.Ticks;
}
- if (validatedTimeout != value)
+ if (timeoutAdjusted)
{
if (etwLog.IsEnabled())
{
From 57018beb30bfead6c43348070504723d8048ab1f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 3 Aug 2026 00:46:53 +0000
Subject: [PATCH 4/4] Simplify TransactionManager timeout logic per @jkotas
feedback
- Delete MachineSettingsSection, ConfigurationStrings, DefaultSettingsSection files
- Remove those files from the .csproj
- Initialize s_defaultTimeoutTicks with TimeSpan.FromMinutes(1).Ticks
- Rename s_maximumTimeout to s_maximumTimeoutTicks (long), initialize with TimeSpan.FromMinutes(10).Ticks
- Delete s_defaultTimeoutValidated and s_cachedMaxTimeout
- Simplify DefaultTimeout getter: direct Interlocked.Read + combined ETW MethodEnter/Exit
- Simplify MaximumTimeout getter: direct Interlocked.Read + combined ETW MethodEnter/Exit
- DefaultTimeout setter: ValidateTimeout inside lock, Interlocked.Exchange, compare validated vs original
- MaximumTimeout setter: set max with Interlocked.Exchange, revalidate default, fire event outside lock
- Inline "" for DistributedTransactionManagerName
Co-authored-by: jkotas <6668460+jkotas@users.noreply.github.com>
---
.../src/System.Transactions.Local.csproj | 3 -
.../Configuration/ConfigurationStrings.cs | 10 ---
.../Configuration/DefaultSettingsSection.cs | 16 -----
.../Configuration/MachineSettingsSection.cs | 14 ----
.../System/Transactions/TransactionManager.cs | 64 +++++--------------
5 files changed, 17 insertions(+), 90 deletions(-)
delete mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/ConfigurationStrings.cs
delete mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/DefaultSettingsSection.cs
delete mode 100644 src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/MachineSettingsSection.cs
diff --git a/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj b/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj
index fb9e5552476c99..812c9f40503335 100644
--- a/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj
+++ b/src/libraries/System.Transactions.Local/src/System.Transactions.Local.csproj
@@ -39,9 +39,6 @@
-
-
-
diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/ConfigurationStrings.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/ConfigurationStrings.cs
deleted file mode 100644
index 0a8cb879bd9490..00000000000000
--- a/src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/ConfigurationStrings.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace System.Transactions.Configuration
-{
- internal static class ConfigurationStrings
- {
- internal const string DefaultDistributedTransactionManagerName = "";
- }
-}
diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/DefaultSettingsSection.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/DefaultSettingsSection.cs
deleted file mode 100644
index 3bc168cc92c6dd..00000000000000
--- a/src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/DefaultSettingsSection.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace System.Transactions.Configuration
-{
- internal sealed class DefaultSettingsSection // ConfigurationSection
- {
- private static readonly DefaultSettingsSection s_section = new DefaultSettingsSection();
-
- internal static DefaultSettingsSection GetSection() => s_section;
-
- public static string DistributedTransactionManagerName { get; set; } = ConfigurationStrings.DefaultDistributedTransactionManagerName;
-
- public static TimeSpan Timeout => TimeSpan.FromMinutes(1);
- }
-}
diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/MachineSettingsSection.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/MachineSettingsSection.cs
deleted file mode 100644
index 080c9343085736..00000000000000
--- a/src/libraries/System.Transactions.Local/src/System/Transactions/Configuration/MachineSettingsSection.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-namespace System.Transactions.Configuration
-{
- internal sealed class MachineSettingsSection // ConfigurationSection
- {
- private static readonly MachineSettingsSection s_section = new MachineSettingsSection();
-
- internal static MachineSettingsSection GetSection() => s_section;
-
- public static TimeSpan MaxTimeout => TimeSpan.FromMinutes(10);
- }
-}
diff --git a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs
index e1b8a6d97423cc..fd0af63655e68e 100644
--- a/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs
+++ b/src/libraries/System.Transactions.Local/src/System/Transactions/TransactionManager.cs
@@ -6,7 +6,6 @@
using System.IO;
using System.Runtime.Versioning;
using System.Threading;
-using System.Transactions.Configuration;
#if WINDOWS
using System.Transactions.DtcProxyShim;
#endif
@@ -279,11 +278,7 @@ internal static IsolationLevel DefaultIsolationLevel
}
}
- private static DefaultSettingsSection DefaultSettings => field ??= DefaultSettingsSection.GetSection();
- private static MachineSettingsSection MachineSettings => field ??= MachineSettingsSection.GetSection();
-
- private static bool s_defaultTimeoutValidated;
- private static long s_defaultTimeoutTicks;
+ private static long s_defaultTimeoutTicks = TimeSpan.FromMinutes(1).Ticks;
public static TimeSpan DefaultTimeout
{
get
@@ -292,22 +287,6 @@ public static TimeSpan DefaultTimeout
if (etwLog.IsEnabled())
{
etwLog.MethodEnter(TraceSourceType.TraceSourceBase, "TransactionManager.get_DefaultTimeout");
- }
-
- if (!s_defaultTimeoutValidated)
- {
- LazyInitializer.EnsureInitialized(ref s_defaultTimeoutTicks, ref s_defaultTimeoutValidated, ref s_classSyncObject, () => ValidateTimeout(DefaultSettingsSection.Timeout).Ticks);
- if (Interlocked.Read(ref s_defaultTimeoutTicks) != DefaultSettingsSection.Timeout.Ticks)
- {
- if (etwLog.IsEnabled())
- {
- etwLog.ConfiguredDefaultTimeoutAdjusted();
- }
- }
- }
-
- if (etwLog.IsEnabled())
- {
etwLog.MethodExit(TraceSourceType.TraceSourceBase, "TransactionManager.get_DefaultTimeout");
}
return new TimeSpan(Interlocked.Read(ref s_defaultTimeoutTicks));
@@ -320,13 +299,12 @@ public static TimeSpan DefaultTimeout
etwLog.MethodEnter(TraceSourceType.TraceSourceBase, "TransactionManager.set_DefaultTimeout");
}
- TimeSpan validatedTimeout = ValidateTimeout(value);
bool timeoutAdjusted;
lock (ClassSyncObject)
{
+ TimeSpan validatedTimeout = ValidateTimeout(value);
Interlocked.Exchange(ref s_defaultTimeoutTicks, validatedTimeout.Ticks);
- s_defaultTimeoutValidated = true;
- timeoutAdjusted = Interlocked.Read(ref s_defaultTimeoutTicks) != value.Ticks;
+ timeoutAdjusted = validatedTimeout != value;
}
if (timeoutAdjusted)
{
@@ -344,8 +322,7 @@ public static TimeSpan DefaultTimeout
}
- private static bool s_cachedMaxTimeout;
- private static TimeSpan s_maximumTimeout;
+ private static long s_maximumTimeoutTicks = TimeSpan.FromMinutes(10).Ticks;
public static TimeSpan MaximumTimeout
{
get
@@ -354,16 +331,9 @@ public static TimeSpan MaximumTimeout
if (etwLog.IsEnabled())
{
etwLog.MethodEnter(TraceSourceType.TraceSourceBase, "TransactionManager.get_DefaultMaximumTimeout");
- }
-
- LazyInitializer.EnsureInitialized(ref s_maximumTimeout, ref s_cachedMaxTimeout, ref s_classSyncObject, () => MachineSettingsSection.MaxTimeout);
-
- if (etwLog.IsEnabled())
- {
etwLog.MethodExit(TraceSourceType.TraceSourceBase, "TransactionManager.get_DefaultMaximumTimeout");
}
-
- return s_maximumTimeout;
+ return new TimeSpan(Interlocked.Read(ref s_maximumTimeoutTicks));
}
set
{
@@ -375,21 +345,21 @@ public static TimeSpan MaximumTimeout
ArgumentOutOfRangeException.ThrowIfLessThan(value, TimeSpan.Zero);
+ bool timeoutAdjusted;
lock (ClassSyncObject)
{
- s_maximumTimeout = value;
- s_cachedMaxTimeout = true;
- LazyInitializer.EnsureInitialized(ref s_defaultTimeoutTicks, ref s_defaultTimeoutValidated, ref s_classSyncObject, () => DefaultSettingsSection.Timeout.Ticks);
+ Interlocked.Exchange(ref s_maximumTimeoutTicks, value.Ticks);
- long defaultTimeoutTicks = Interlocked.Read(ref s_defaultTimeoutTicks);
- long validatedDefaultTicks = ValidateTimeout(new TimeSpan(defaultTimeoutTicks)).Ticks;
- if (validatedDefaultTicks != defaultTimeoutTicks)
+ TimeSpan timeout = new TimeSpan(s_defaultTimeoutTicks);
+ TimeSpan validatedTimeout = ValidateTimeout(timeout);
+ Interlocked.Exchange(ref s_defaultTimeoutTicks, validatedTimeout.Ticks);
+ timeoutAdjusted = validatedTimeout != timeout;
+ }
+ if (timeoutAdjusted)
+ {
+ if (etwLog.IsEnabled())
{
- Interlocked.Exchange(ref s_defaultTimeoutTicks, validatedDefaultTicks);
- if (etwLog.IsEnabled())
- {
- etwLog.ConfiguredDefaultTimeoutAdjusted();
- }
+ etwLog.ConfiguredDefaultTimeoutAdjusted();
}
}
@@ -624,6 +594,6 @@ internal static Transaction FindOrCreatePromotedTransaction(Guid transactionIden
internal static OletxTransactionManager DistributedTransactionManager =>
// If the distributed transaction manager is not configured, throw an exception
LazyInitializer.EnsureInitialized(ref distributedTransactionManager, ref s_classSyncObject,
- () => new OletxTransactionManager(DefaultSettingsSection.DistributedTransactionManagerName));
+ () => new OletxTransactionManager(""));
}
}