Skip to content

Commit 3b4347e

Browse files
authored
Transaction.Timeout property (#129)
* Transaction.Timeout property * Optimize Transaction ctor * Optoimize Transaction * Optimize Transaction.Guid * Refactoring: PreDbCommandExecuting() method * Optimize Transaction.Outermost * Add missed cancellationToken
1 parent 5ce9582 commit 3b4347e

File tree

5 files changed

+95
-78
lines changed

5 files changed

+95
-78
lines changed

Orm/Xtensive.Orm/Orm/Providers/StorageDriver.Operations.cs

+19-15
Original file line numberDiff line numberDiff line change
@@ -397,26 +397,26 @@ public async ValueTask ReleaseSavepointAsync(
397397
#region Sync Execute methods
398398

399399
public int ExecuteNonQuery(Session session, DbCommand command) =>
400-
ExecuteCommand(session, command, CommandBehavior.Default, (c, cb) => c.ExecuteNonQuery());
400+
ExecuteCommand(session, command, CommandBehavior.Default, static (c, cb) => c.ExecuteNonQuery());
401401

402402
public object ExecuteScalar(Session session, DbCommand command) =>
403-
ExecuteCommand(session, command, CommandBehavior.Default, (c, cb) => c.ExecuteScalar());
403+
ExecuteCommand(session, command, CommandBehavior.Default, static (c, cb) => c.ExecuteScalar());
404404

405405
public DbDataReader ExecuteReader(Session session, DbCommand command,
406406
CommandBehavior behavior = CommandBehavior.Default) =>
407-
ExecuteCommand(session, command, behavior, (c, cb) => c.ExecuteReader(cb));
407+
ExecuteCommand(session, command, behavior, static (c, cb) => c.ExecuteReader(cb));
408408

409409
#endregion
410410

411411
#region Async Execute methods
412412

413413
public Task<int> ExecuteNonQueryAsync(Session session, DbCommand command, CancellationToken cancellationToken = default) =>
414414
ExecuteCommandAsync(session, command, CommandBehavior.Default, cancellationToken,
415-
(c, cb, ct) => c.ExecuteNonQueryAsync(ct));
415+
static (c, cb, ct) => c.ExecuteNonQueryAsync(ct));
416416

417417
public Task<object> ExecuteScalarAsync(Session session, DbCommand command, CancellationToken cancellationToken = default) =>
418418
ExecuteCommandAsync(session, command, CommandBehavior.Default, cancellationToken,
419-
(c, cb, ct) => c.ExecuteScalarAsync(ct));
419+
static (c, cb, ct) => c.ExecuteScalarAsync(ct));
420420

421421
public Task<DbDataReader> ExecuteReaderAsync(Session session, DbCommand command,
422422
CancellationToken cancellationToken = default) =>
@@ -425,18 +425,27 @@ public Task<DbDataReader> ExecuteReaderAsync(Session session, DbCommand command,
425425
public Task<DbDataReader> ExecuteReaderAsync(
426426
Session session, DbCommand command, CommandBehavior behavior, CancellationToken cancellationToken = default) =>
427427
ExecuteCommandAsync(session, command, behavior, cancellationToken,
428-
(c, cb, ct) => c.ExecuteReaderAsync(cb, ct));
428+
static (c, cb, ct) => c.ExecuteReaderAsync(cb, ct));
429429

430430
#endregion
431431

432-
private TResult ExecuteCommand<TResult>(
433-
Session session, DbCommand command, CommandBehavior commandBehavior, Func<DbCommand, CommandBehavior, TResult> action)
432+
private void PreDbCommandExecuting(Session session, DbCommand command, CancellationToken ct = default)
434433
{
435434
if (isLoggingEnabled) {
436435
SqlLog.Info(nameof(Strings.LogSessionXQueryY), session.ToStringSafely(), command.ToHumanReadableString());
437436
}
438437

439-
session?.Events.NotifyDbCommandExecuting(command);
438+
ct.ThrowIfCancellationRequested();
439+
if (session is not null) {
440+
session.Transaction?.CheckForTimeout(command);
441+
session.Events.NotifyDbCommandExecuting(command);
442+
}
443+
}
444+
445+
private TResult ExecuteCommand<TResult>(
446+
Session session, DbCommand command, CommandBehavior commandBehavior, Func<DbCommand, CommandBehavior, TResult> action)
447+
{
448+
PreDbCommandExecuting(session, command);
440449

441450
TResult result;
442451
try {
@@ -457,12 +466,7 @@ private async Task<TResult> ExecuteCommandAsync<TResult>(Session session,
457466
DbCommand command, CommandBehavior commandBehavior,
458467
CancellationToken cancellationToken, Func<DbCommand, CommandBehavior, CancellationToken, Task<TResult>> action)
459468
{
460-
if (isLoggingEnabled) {
461-
SqlLog.Info(nameof(Strings.LogSessionXQueryY), session.ToStringSafely(), command.ToHumanReadableString());
462-
}
463-
464-
cancellationToken.ThrowIfCancellationRequested();
465-
session?.Events.NotifyDbCommandExecuting(command);
469+
PreDbCommandExecuting(session, command, cancellationToken);
466470

467471
TResult result;
468472
try {

Orm/Xtensive.Orm/Orm/Session.Transactions.cs

+1-6
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ namespace Xtensive.Orm
1818
{
1919
public partial class Session
2020
{
21-
private const string SavepointNameFormat = "s{0}";
22-
2321
private readonly StateLifetimeToken sessionLifetimeToken = new StateLifetimeToken();
2422
private int nextSavepoint;
2523

@@ -407,10 +405,7 @@ private void EnsureIsolationLevelCompatibility(IsolationLevel current, Isolation
407405
throw new InvalidOperationException(Strings.ExCanNotReuseOpenedTransactionRequestedIsolationLevelIsDifferent);
408406
}
409407

410-
private string GetNextSavepointName()
411-
{
412-
return string.Format(SavepointNameFormat, nextSavepoint++);
413-
}
408+
private string GetNextSavepointName() => $"s{nextSavepoint++}";
414409

415410
private void ClearChangeRegistry()
416411
{

Orm/Xtensive.Orm/Orm/Transaction.cs

+44-50
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
using System;
88
using System.Collections.Generic;
9+
using System.Data.Common;
910
using System.Threading;
1011
using System.Threading.Tasks;
1112
using System.Transactions;
@@ -28,12 +29,7 @@ public sealed partial class Transaction : IHasExtensions
2829
/// Gets the current <see cref="Transaction"/> object
2930
/// using <see cref="Session"/>.<see cref="Orm.Session.Current"/>.
3031
/// </summary>
31-
public static Transaction Current {
32-
get {
33-
var session = Session.Current;
34-
return session?.Transaction;
35-
}
36-
}
32+
public static Transaction Current => Session.Current?.Transaction;
3733

3834
/// <summary>
3935
/// Gets the current <see cref="Transaction"/>,
@@ -44,16 +40,8 @@ public static Transaction Current {
4440
/// <exception cref="InvalidOperationException">
4541
/// <see cref="Transaction.Current"/> <see cref="Transaction"/> is <see langword="null" />.
4642
/// </exception>
47-
public static Transaction Demand()
48-
{
49-
var current = Current;
50-
if (current == null) {
51-
throw new InvalidOperationException(
52-
Strings.ExActiveTransactionIsRequiredForThisOperationUseSessionOpenTransactionToOpenIt);
53-
}
54-
55-
return current;
56-
}
43+
public static Transaction Demand() =>
44+
Current ?? throw new InvalidOperationException(Strings.ExActiveTransactionIsRequiredForThisOperationUseSessionOpenTransactionToOpenIt);
5745

5846
/// <summary>
5947
/// Checks whether a transaction exists or not in the provided session.
@@ -68,58 +56,70 @@ public static void Require(Session session)
6856

6957
#endregion
7058

71-
private readonly List<StateLifetimeToken> lifetimeTokens;
59+
private readonly List<StateLifetimeToken> lifetimeTokens = new(1);
7260

7361
private ExtensionCollection extensions;
7462
private Transaction inner;
7563

7664
/// <summary>
7765
/// Gets a value indicating whether this instance is automatic transaction.
7866
/// </summary>
79-
public bool IsAutomatic { get; private set; }
80-
67+
public bool IsAutomatic { get; }
68+
8169
/// <summary>
8270
/// Gets a value indicating whether this instance is
8371
/// transaction running locally.
8472
/// </summary>
85-
public bool IsDisconnected { get; private set; }
86-
73+
public bool IsDisconnected { get; }
74+
75+
private Guid? guid;
8776
/// <summary>
8877
/// Gets the unique identifier of this transaction.
8978
/// Nested transactions have the same <see cref="Guid"/>
9079
/// as their outermost.
9180
/// </summary>
92-
public Guid Guid { get; private set; }
81+
public Guid Guid => Outer?.Guid ?? (guid ??= Guid.NewGuid());
9382

9483
/// <summary>
9584
/// Gets the session this transaction is bound to.
9685
/// </summary>
97-
public Session Session { get; private set; }
86+
public Session Session { get; }
9887

9988
/// <summary>
10089
/// Gets the isolation level.
10190
/// </summary>
102-
public IsolationLevel IsolationLevel { get; private set; }
91+
public IsolationLevel IsolationLevel { get; }
10392

10493
/// <summary>
10594
/// Gets the state of the transaction.
10695
/// </summary>
107-
public TransactionState State { get; private set; }
96+
public TransactionState State { get; private set; } = TransactionState.NotActivated;
10897

10998
/// <summary>
11099
/// Gets the outer transaction.
111100
/// </summary>
112-
public Transaction Outer { get; private set; }
101+
public Transaction Outer { get; }
113102

114103
/// <summary>
115104
/// Gets the outermost transaction.
116105
/// </summary>
117-
public Transaction Outermost { get; private set; }
106+
public Transaction Outermost => Outer?.Outermost ?? this;
118107

119108
/// <summary>
120109
/// Gets the start time of this transaction.
121110
/// </summary>
122-
public DateTime TimeStamp { get; private set; }
111+
public DateTime TimeStamp { get; } = DateTime.UtcNow;
112+
113+
private TimeSpan? timeout;
114+
/// <summary>
115+
/// Gets or sets Transaction timeout
116+
/// </summary>
117+
public TimeSpan? Timeout {
118+
get => timeout;
119+
set => timeout = IsNested
120+
? throw new InvalidOperationException(Strings.ExNestedTransactionTimeout)
121+
: value;
122+
}
123123

124124
/// <summary>
125125
/// Gets a value indicating whether this transaction is a nested transaction.
@@ -129,7 +129,7 @@ public static void Require(Session session)
129129
/// <summary>
130130
/// Gets <see cref="StateLifetimeToken"/> associated with this transaction.
131131
/// </summary>
132-
public StateLifetimeToken LifetimeToken { get; private set; }
132+
public StateLifetimeToken LifetimeToken { get; private set; } = new();
133133

134134
#region IHasExtensions Members
135135

@@ -138,7 +138,7 @@ public static void Require(Session session)
138138

139139
#endregion
140140

141-
internal string SavepointName { get; private set; }
141+
internal string SavepointName { get; }
142142

143143
/// <summary>
144144
/// Indicates whether changes made in this transaction are visible "as is"
@@ -278,40 +278,34 @@ private void ClearLifetimeTokens()
278278
LifetimeToken = null;
279279
}
280280

281+
internal void CheckForTimeout(DbCommand command)
282+
{
283+
if (Timeout is not null) {
284+
var remain = TimeStamp + Timeout.Value - DateTime.UtcNow;
285+
command.CommandTimeout = remain.Ticks > 0
286+
? Math.Max(1, (int) remain.TotalSeconds)
287+
: throw new TimeoutException(String.Format(Strings.ExTransactionTimeout, Timeout));
288+
}
289+
}
290+
281291
#endregion
282292

283-
284-
// Constructors
285293

286-
internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic)
287-
: this(session, isolationLevel, isAutomatic, null, null)
288-
{
289-
}
294+
// Constructors
290295

291-
internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic, Transaction outer,
292-
string savepointName)
296+
internal Transaction(Session session, IsolationLevel isolationLevel, bool isAutomatic, Transaction outer = null,
297+
string savepointName = null)
293298
{
294-
lifetimeTokens = new List<StateLifetimeToken>();
295-
296-
Guid = Guid.NewGuid();
297-
State = TransactionState.NotActivated;
298299
Session = session;
299300
IsolationLevel = isolationLevel;
300301
IsAutomatic = isAutomatic;
301302
IsDisconnected = session.IsDisconnected;
302-
TimeStamp = DateTime.UtcNow;
303-
LifetimeToken = new StateLifetimeToken();
304303
lifetimeTokens.Add(LifetimeToken);
305304

306305
if (outer != null) {
307306
Outer = outer;
308-
Guid = outer.Guid;
309-
Outermost = outer.Outermost;
310307
SavepointName = savepointName;
311308
}
312-
else {
313-
Outermost = this;
314-
}
315309
}
316310
}
317-
}
311+
}

Orm/Xtensive.Orm/Strings.Designer.cs

+25-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)