From e0852aafb03e4ffa16c99e96aeec6cbbfe488f69 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Thu, 29 Jun 2023 16:58:26 -0700 Subject: [PATCH 1/7] Transaction.Timeout property --- .../Orm/Providers/StorageDriver.Operations.cs | 2 + Orm/Xtensive.Orm/Orm/Transaction.cs | 69 +++++++++++-------- Orm/Xtensive.Orm/Strings.Designer.cs | 32 +++++++-- Orm/Xtensive.Orm/Strings.resx | 6 ++ 4 files changed, 72 insertions(+), 37 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs index 1d3245ba18..20f8442eb6 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs @@ -436,6 +436,7 @@ private TResult ExecuteCommand( SqlLog.Info(nameof(Strings.LogSessionXQueryY), session.ToStringSafely(), command.ToHumanReadableString()); } + session?.Transaction?.CheckForTimeout(command); session?.Events.NotifyDbCommandExecuting(command); TResult result; @@ -462,6 +463,7 @@ private async Task ExecuteCommandAsync(Session session, } cancellationToken.ThrowIfCancellationRequested(); + session?.Transaction?.CheckForTimeout(command); session?.Events.NotifyDbCommandExecuting(command); TResult result; diff --git a/Orm/Xtensive.Orm/Orm/Transaction.cs b/Orm/Xtensive.Orm/Orm/Transaction.cs index f3cb650fe3..414c4f259b 100644 --- a/Orm/Xtensive.Orm/Orm/Transaction.cs +++ b/Orm/Xtensive.Orm/Orm/Transaction.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Data.Common; using System.Threading; using System.Threading.Tasks; using System.Transactions; @@ -28,12 +29,7 @@ public sealed partial class Transaction : IHasExtensions /// Gets the current object /// using .. /// - public static Transaction Current { - get { - var session = Session.Current; - return session?.Transaction; - } - } + public static Transaction Current => Session.Current?.Transaction; /// /// Gets the current , @@ -44,16 +40,8 @@ public static Transaction Current { /// /// is . /// - public static Transaction Demand() - { - var current = Current; - if (current == null) { - throw new InvalidOperationException( - Strings.ExActiveTransactionIsRequiredForThisOperationUseSessionOpenTransactionToOpenIt); - } - - return current; - } + public static Transaction Demand() => + Current ?? throw new InvalidOperationException(Strings.ExActiveTransactionIsRequiredForThisOperationUseSessionOpenTransactionToOpenIt); /// /// Checks whether a transaction exists or not in the provided session. @@ -76,30 +64,30 @@ public static void Require(Session session) /// /// Gets a value indicating whether this instance is automatic transaction. /// - public bool IsAutomatic { get; private set; } - + public bool IsAutomatic { get; } + /// /// Gets a value indicating whether this instance is /// transaction running locally. /// - public bool IsDisconnected { get; private set; } - + public bool IsDisconnected { get; } + /// /// Gets the unique identifier of this transaction. /// Nested transactions have the same /// as their outermost. /// - public Guid Guid { get; private set; } + public Guid Guid { get; } /// /// Gets the session this transaction is bound to. /// - public Session Session { get; private set; } + public Session Session { get; } /// /// Gets the isolation level. /// - public IsolationLevel IsolationLevel { get; private set; } + public IsolationLevel IsolationLevel { get; } /// /// Gets the state of the transaction. @@ -109,17 +97,28 @@ public static void Require(Session session) /// /// Gets the outer transaction. /// - public Transaction Outer { get; private set; } + public Transaction Outer { get; } /// /// Gets the outermost transaction. /// - public Transaction Outermost { get; private set; } + public Transaction Outermost { get; } /// /// Gets the start time of this transaction. /// - public DateTime TimeStamp { get; private set; } + public DateTime TimeStamp { get; } + + private TimeSpan? timeout; + /// + /// Gets or sets Transaction timeout + /// + public TimeSpan? Timeout { + get => timeout; + set => timeout = IsNested + ? throw new InvalidOperationException(Strings.ExNestedTransactionTimeout) + : value; + } /// /// Gets a value indicating whether this transaction is a nested transaction. @@ -138,7 +137,7 @@ public static void Require(Session session) #endregion - internal string SavepointName { get; private set; } + internal string SavepointName { get; } /// /// Indicates whether changes made in this transaction are visible "as is" @@ -278,9 +277,19 @@ private void ClearLifetimeTokens() LifetimeToken = null; } + internal void CheckForTimeout(DbCommand command) + { + if (Timeout is not null) { + var remain = TimeStamp + Timeout.Value - DateTime.UtcNow; + command.CommandTimeout = remain.Ticks > 0 + ? Math.Max(1, (int) remain.TotalSeconds) + : throw new TimeoutException(String.Format(Strings.ExTransactionTimeout, Timeout)); + } + } + #endregion - + // Constructors internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic) @@ -293,7 +302,6 @@ internal Transaction(Session session, IsolationLevel isolationLevel, bool isAuto { lifetimeTokens = new List(); - Guid = Guid.NewGuid(); State = TransactionState.NotActivated; Session = session; IsolationLevel = isolationLevel; @@ -310,8 +318,9 @@ internal Transaction(Session session, IsolationLevel isolationLevel, bool isAuto SavepointName = savepointName; } else { + Guid = Guid.NewGuid(); Outermost = this; } } } -} +} diff --git a/Orm/Xtensive.Orm/Strings.Designer.cs b/Orm/Xtensive.Orm/Strings.Designer.cs index d3f712af8a..a9b5c80878 100644 --- a/Orm/Xtensive.Orm/Strings.Designer.cs +++ b/Orm/Xtensive.Orm/Strings.Designer.cs @@ -1360,7 +1360,7 @@ internal static string ExDateOnlyToStringMethodIsNotSupported { return ResourceManager.GetString("ExDateOnlyToStringMethodIsNotSupported", resourceCulture); } } - + /// /// Looks up a localized string similar to DateTime.ToString() method is not supported, use the DateTime.ToString("s").. /// @@ -1974,7 +1974,7 @@ internal static string ExIgnoreRuleIsAlreadyConfiguredForX { return ResourceManager.GetString("ExIgnoreRuleIsAlreadyConfiguredForX", resourceCulture); } } - + /// /// Looks up a localized string similar to Ignore rule '{0}' must be applied to column, index or table.. /// @@ -2848,7 +2848,7 @@ internal static string ExMaxNumberOfConditionsShouldBeBetweenXAndY { return ResourceManager.GetString("ExMaxNumberOfConditionsShouldBeBetweenXAndY", resourceCulture); } } - + /// /// Looks up a localized string similar to Measurement is already completed.. /// @@ -3084,6 +3084,15 @@ internal static string ExNestedFieldXIsNotSupported { } } + /// + /// Looks up a localized string similar to Nested transaction cannot have timeout. + /// + internal static string ExNestedTransactionTimeout { + get { + return ResourceManager.GetString("ExNestedTransactionTimeout", resourceCulture); + } + } + /// /// Looks up a localized string similar to Comparer.Current is null.. /// @@ -4325,7 +4334,7 @@ internal static string ExTimeOnlyToStringMethodIsNotSupported { return ResourceManager.GetString("ExTimeOnlyToStringMethodIsNotSupported", resourceCulture); } } - + /// /// Looks up a localized string similar to Transaction is not active.. /// @@ -4353,6 +4362,15 @@ internal static string ExTransactionShouldNotBeActive { } } + /// + /// Looks up a localized string similar to Transaction is longer than {0}. + /// + internal static string ExTransactionTimeout { + get { + return ResourceManager.GetString("ExTransactionTimeout", resourceCulture); + } + } + /// /// Looks up a localized string similar to Translation of DateOnly.ToString(string) with arbitrary arguments is not supported. Use DateOnly.ToString("s").. /// @@ -4361,7 +4379,7 @@ internal static string ExTranslationOfDateOnlyToStringWithArbitraryArgumentIsNot return ResourceManager.GetString("ExTranslationOfDateOnlyToStringWithArbitraryArgumentIsNotSupported", resourceCulture); } } - + /// /// Looks up a localized string similar to Translation of DateTime.ToString(string) with arbitrary arguments is not supported. Use DateTime.ToString("s").. /// @@ -4397,7 +4415,7 @@ internal static string ExTranslationOfTimeOnlyToStringWithArbitraryArgumentIsNot return ResourceManager.GetString("ExTranslationOfTimeOnlyToStringWithArbitraryArgumentIsNotSupported", resourceCulture); } } - + /// /// Looks up a localized string similar to Translation of {0} method does not support any parameter type, but {1}.. /// @@ -4659,7 +4677,7 @@ internal static string ExUnableToActualizeSchemaNodeInQuery { return ResourceManager.GetString("ExUnableToActualizeSchemaNodeInQuery", resourceCulture); } } - + /// /// Looks up a localized string similar to Unable to apply VersionAttribute with VersionMode.Auto or Version.Mode.Manual mode set on field {0} of type {1}. Only VersionMode.Skip is allowed.. /// diff --git a/Orm/Xtensive.Orm/Strings.resx b/Orm/Xtensive.Orm/Strings.resx index a51e6c2856..4b88ac0739 100644 --- a/Orm/Xtensive.Orm/Strings.resx +++ b/Orm/Xtensive.Orm/Strings.resx @@ -2603,4 +2603,10 @@ Error: {1} DomainConfiguration.MaxNumberOfConditions should be between {0} and {1} (included). + + Nested transaction cannot have timeout + + + Transaction is longer than {0} + \ No newline at end of file From de362c3c5ab102a1f15548fcdc3c72d3ad0ee916 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Thu, 29 Jun 2023 17:37:12 -0700 Subject: [PATCH 2/7] Optimize Transaction ctor --- Orm/Xtensive.Orm/Orm/Transaction.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Transaction.cs b/Orm/Xtensive.Orm/Orm/Transaction.cs index 414c4f259b..601dbf9ff6 100644 --- a/Orm/Xtensive.Orm/Orm/Transaction.cs +++ b/Orm/Xtensive.Orm/Orm/Transaction.cs @@ -300,8 +300,6 @@ internal Transaction(Session session, IsolationLevel isolationLevel, bool isAuto internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic, Transaction outer, string savepointName) { - lifetimeTokens = new List(); - State = TransactionState.NotActivated; Session = session; IsolationLevel = isolationLevel; @@ -309,7 +307,7 @@ internal Transaction(Session session, IsolationLevel isolationLevel, bool isAuto IsDisconnected = session.IsDisconnected; TimeStamp = DateTime.UtcNow; LifetimeToken = new StateLifetimeToken(); - lifetimeTokens.Add(LifetimeToken); + lifetimeTokens = new List { LifetimeToken }; if (outer != null) { Outer = outer; From c223efa7cdcdae3220cfe5c4e26715e051422b79 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Thu, 29 Jun 2023 17:41:59 -0700 Subject: [PATCH 3/7] Optoimize Transaction --- Orm/Xtensive.Orm/Orm/Transaction.cs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Transaction.cs b/Orm/Xtensive.Orm/Orm/Transaction.cs index 601dbf9ff6..8950c18078 100644 --- a/Orm/Xtensive.Orm/Orm/Transaction.cs +++ b/Orm/Xtensive.Orm/Orm/Transaction.cs @@ -56,7 +56,7 @@ public static void Require(Session session) #endregion - private readonly List lifetimeTokens; + private readonly List lifetimeTokens = new(1); private ExtensionCollection extensions; private Transaction inner; @@ -92,7 +92,7 @@ public static void Require(Session session) /// /// Gets the state of the transaction. /// - public TransactionState State { get; private set; } + public TransactionState State { get; private set; } = TransactionState.NotActivated; /// /// Gets the outer transaction. @@ -107,7 +107,7 @@ public static void Require(Session session) /// /// Gets the start time of this transaction. /// - public DateTime TimeStamp { get; } + public DateTime TimeStamp { get; } = DateTime.UtcNow; private TimeSpan? timeout; /// @@ -128,7 +128,7 @@ public TimeSpan? Timeout { /// /// Gets associated with this transaction. /// - public StateLifetimeToken LifetimeToken { get; private set; } + public StateLifetimeToken LifetimeToken { get; private set; } = new(); #region IHasExtensions Members @@ -292,22 +292,14 @@ internal void CheckForTimeout(DbCommand command) // Constructors - internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic) - : this(session, isolationLevel, isAutomatic, null, null) + internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic, Transaction outer = null, + string savepointName = null) { - } - - internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic, Transaction outer, - string savepointName) - { - State = TransactionState.NotActivated; Session = session; IsolationLevel = isolationLevel; IsAutomatic = isAutomatic; IsDisconnected = session.IsDisconnected; - TimeStamp = DateTime.UtcNow; - LifetimeToken = new StateLifetimeToken(); - lifetimeTokens = new List { LifetimeToken }; + lifetimeTokens.Add(LifetimeToken); if (outer != null) { Outer = outer; From 92b9f89c785a77c4edc6d9e1603e25049dcb74ae Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Thu, 29 Jun 2023 17:46:48 -0700 Subject: [PATCH 4/7] Optimize Transaction.Guid --- Orm/Xtensive.Orm/Orm/Transaction.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Transaction.cs b/Orm/Xtensive.Orm/Orm/Transaction.cs index 8950c18078..57234faa6f 100644 --- a/Orm/Xtensive.Orm/Orm/Transaction.cs +++ b/Orm/Xtensive.Orm/Orm/Transaction.cs @@ -72,12 +72,13 @@ public static void Require(Session session) /// public bool IsDisconnected { get; } + private Guid? guid; /// /// Gets the unique identifier of this transaction. /// Nested transactions have the same /// as their outermost. /// - public Guid Guid { get; } + public Guid Guid => Outer?.Guid ?? (guid ??= Guid.NewGuid()); /// /// Gets the session this transaction is bound to. @@ -303,12 +304,10 @@ internal Transaction(Session session, IsolationLevel isolationLevel, bool isAuto if (outer != null) { Outer = outer; - Guid = outer.Guid; Outermost = outer.Outermost; SavepointName = savepointName; } else { - Guid = Guid.NewGuid(); Outermost = this; } } From b9c89522d45b66aab66d489725a7c7a8c19a2537 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Thu, 29 Jun 2023 20:39:50 -0700 Subject: [PATCH 5/7] Refactoring: PreDbCommandExecuting() method --- .../Orm/Providers/StorageDriver.Operations.cs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs index 20f8442eb6..83a89213f0 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs @@ -397,14 +397,14 @@ public async ValueTask ReleaseSavepointAsync( #region Sync Execute methods public int ExecuteNonQuery(Session session, DbCommand command) => - ExecuteCommand(session, command, CommandBehavior.Default, (c, cb) => c.ExecuteNonQuery()); + ExecuteCommand(session, command, CommandBehavior.Default, static (c, cb) => c.ExecuteNonQuery()); public object ExecuteScalar(Session session, DbCommand command) => - ExecuteCommand(session, command, CommandBehavior.Default, (c, cb) => c.ExecuteScalar()); + ExecuteCommand(session, command, CommandBehavior.Default, static (c, cb) => c.ExecuteScalar()); public DbDataReader ExecuteReader(Session session, DbCommand command, CommandBehavior behavior = CommandBehavior.Default) => - ExecuteCommand(session, command, behavior, (c, cb) => c.ExecuteReader(cb)); + ExecuteCommand(session, command, behavior, static (c, cb) => c.ExecuteReader(cb)); #endregion @@ -412,11 +412,11 @@ public DbDataReader ExecuteReader(Session session, DbCommand command, public Task ExecuteNonQueryAsync(Session session, DbCommand command, CancellationToken cancellationToken = default) => ExecuteCommandAsync(session, command, CommandBehavior.Default, cancellationToken, - (c, cb, ct) => c.ExecuteNonQueryAsync(ct)); + static (c, cb, ct) => c.ExecuteNonQueryAsync(ct)); public Task ExecuteScalarAsync(Session session, DbCommand command, CancellationToken cancellationToken = default) => ExecuteCommandAsync(session, command, CommandBehavior.Default, cancellationToken, - (c, cb, ct) => c.ExecuteScalarAsync(ct)); + static (c, cb, ct) => c.ExecuteScalarAsync(ct)); public Task ExecuteReaderAsync(Session session, DbCommand command, CancellationToken cancellationToken = default) => @@ -425,19 +425,27 @@ public Task ExecuteReaderAsync(Session session, DbCommand command, public Task ExecuteReaderAsync( Session session, DbCommand command, CommandBehavior behavior, CancellationToken cancellationToken = default) => ExecuteCommandAsync(session, command, behavior, cancellationToken, - (c, cb, ct) => c.ExecuteReaderAsync(cb, ct)); + static (c, cb, ct) => c.ExecuteReaderAsync(cb, ct)); #endregion - private TResult ExecuteCommand( - Session session, DbCommand command, CommandBehavior commandBehavior, Func action) + private void PreDbCommandExecuting(Session session, DbCommand command, CancellationToken ct = default) { if (isLoggingEnabled) { SqlLog.Info(nameof(Strings.LogSessionXQueryY), session.ToStringSafely(), command.ToHumanReadableString()); } - session?.Transaction?.CheckForTimeout(command); - session?.Events.NotifyDbCommandExecuting(command); + ct.ThrowIfCancellationRequested(); + if (session is not null) { + session.Transaction?.CheckForTimeout(command); + session.Events.NotifyDbCommandExecuting(command); + } + } + + private TResult ExecuteCommand( + Session session, DbCommand command, CommandBehavior commandBehavior, Func action) + { + PreDbCommandExecuting(session, command); TResult result; try { @@ -458,13 +466,7 @@ private async Task ExecuteCommandAsync(Session session, DbCommand command, CommandBehavior commandBehavior, CancellationToken cancellationToken, Func> action) { - if (isLoggingEnabled) { - SqlLog.Info(nameof(Strings.LogSessionXQueryY), session.ToStringSafely(), command.ToHumanReadableString()); - } - - cancellationToken.ThrowIfCancellationRequested(); - session?.Transaction?.CheckForTimeout(command); - session?.Events.NotifyDbCommandExecuting(command); + PreDbCommandExecuting(session, command); TResult result; try { From 438e71350a773cd6a91594d4c0d1d51efa4aefb8 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Thu, 29 Jun 2023 20:56:03 -0700 Subject: [PATCH 6/7] Optimize Transaction.Outermost --- Orm/Xtensive.Orm/Orm/Session.Transactions.cs | 7 +------ Orm/Xtensive.Orm/Orm/Transaction.cs | 6 +----- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Session.Transactions.cs b/Orm/Xtensive.Orm/Orm/Session.Transactions.cs index 0da0b91e2b..ad1c22ced5 100644 --- a/Orm/Xtensive.Orm/Orm/Session.Transactions.cs +++ b/Orm/Xtensive.Orm/Orm/Session.Transactions.cs @@ -18,8 +18,6 @@ namespace Xtensive.Orm { public partial class Session { - private const string SavepointNameFormat = "s{0}"; - private readonly StateLifetimeToken sessionLifetimeToken = new StateLifetimeToken(); private int nextSavepoint; @@ -407,10 +405,7 @@ private void EnsureIsolationLevelCompatibility(IsolationLevel current, Isolation throw new InvalidOperationException(Strings.ExCanNotReuseOpenedTransactionRequestedIsolationLevelIsDifferent); } - private string GetNextSavepointName() - { - return string.Format(SavepointNameFormat, nextSavepoint++); - } + private string GetNextSavepointName() => $"s{nextSavepoint++}"; private void ClearChangeRegistry() { diff --git a/Orm/Xtensive.Orm/Orm/Transaction.cs b/Orm/Xtensive.Orm/Orm/Transaction.cs index 57234faa6f..ae04be0591 100644 --- a/Orm/Xtensive.Orm/Orm/Transaction.cs +++ b/Orm/Xtensive.Orm/Orm/Transaction.cs @@ -103,7 +103,7 @@ public static void Require(Session session) /// /// Gets the outermost transaction. /// - public Transaction Outermost { get; } + public Transaction Outermost => Outer?.Outermost ?? this; /// /// Gets the start time of this transaction. @@ -304,12 +304,8 @@ internal Transaction(Session session, IsolationLevel isolationLevel, bool isAuto if (outer != null) { Outer = outer; - Outermost = outer.Outermost; SavepointName = savepointName; } - else { - Outermost = this; - } } } } From 290b7abcab0c858b968ae59365edda67b75c0f77 Mon Sep 17 00:00:00 2001 From: Sergei Pavlov Date: Thu, 29 Jun 2023 21:03:27 -0700 Subject: [PATCH 7/7] Add missed cancellationToken --- Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs index 83a89213f0..90d896e2bd 100644 --- a/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs +++ b/Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs @@ -466,7 +466,7 @@ private async Task ExecuteCommandAsync(Session session, DbCommand command, CommandBehavior commandBehavior, CancellationToken cancellationToken, Func> action) { - PreDbCommandExecuting(session, command); + PreDbCommandExecuting(session, command, cancellationToken); TResult result; try {