Skip to content

Commit a0f9ae2

Browse files
Allow more control of synchronization failures
1 parent 68ad00e commit a0f9ae2

File tree

11 files changed

+139
-18
lines changed

11 files changed

+139
-18
lines changed

build-common/teamcity-hibernate.cfg.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,7 @@
2626
<property name="odbc.explicit_datetime_scale"></property>
2727
<property name="oracle.use_n_prefixed_types_for_unicode"></property>
2828
<property name="query.default_cast_length"></property>
29+
<property name="transaction.ignore_session_synchronization_failures"></property>
30+
<property name="transaction.system_completion_lock_timeout"></property>
2931
</session-factory>
3032
</hibernate-configuration>

default.build

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,26 @@
139139
<namespace prefix="hbm" uri="urn:nhibernate-configuration-2.2" />
140140
</namespaces>
141141
</xmlpoke>
142+
143+
<!-- Make sure the property exists - it's only set for some scenarios. -->
144+
<property name="nhibernate.transaction.ignore_session_synchronization_failures" value="" unless="${property::exists('nhibernate.transaction.ignore_session_synchronization_failures')}"/>
145+
<xmlpoke file="${app.config}"
146+
xpath="//*/hbm:property[@name='transaction.ignore_session_synchronization_failures']"
147+
value="${nhibernate.transaction.ignore_session_synchronization_failures}">
148+
<namespaces>
149+
<namespace prefix="hbm" uri="urn:nhibernate-configuration-2.2" />
150+
</namespaces>
151+
</xmlpoke>
152+
153+
<!-- Make sure the property exists - it's only set for some scenarios. -->
154+
<property name="nhibernate.transaction.system_completion_lock_timeout" value="" unless="${property::exists('nhibernate.transaction.system_completion_lock_timeout')}"/>
155+
<xmlpoke file="${app.config}"
156+
xpath="//*/hbm:property[@name='transaction.system_completion_lock_timeout']"
157+
value="${nhibernate.transaction.system_completion_lock_timeout}">
158+
<namespaces>
159+
<namespace prefix="hbm" uri="urn:nhibernate-configuration-2.2" />
160+
</namespaces>
161+
</xmlpoke>
142162
</target>
143163

144164
<target name="put-connection-settings-into-app-config">

doc/reference/modules/configuration.xml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,8 +1050,8 @@ var session = sessions.OpenSession(conn);
10501050
after scope disposal. This occurs when the transaction is distributed.
10511051
This notably concerns <literal>ISessionImplementor.AfterTransactionCompletion(bool, ITransaction)</literal>.
10521052
NHibernate protects the session from being concurrently used by the code following the scope disposal
1053-
with a lock. To prevent any application freeze, this lock has a default timeout of five seconds. If the
1054-
application appears to require longer (!) running transaction completion events, this setting allows to
1053+
with a lock. To prevent any application freeze, this lock has a default timeout of one second. If the
1054+
application appears to require longer running transaction completion events, this setting allows to
10551055
raise this timeout. <literal>-1</literal> disables the timeout.
10561056
</para>
10571057
<para>
@@ -1060,6 +1060,33 @@ var session = sessions.OpenSession(conn);
10601060
</para>
10611061
</entry>
10621062
</row>
1063+
<row>
1064+
<entry>
1065+
<literal>transaction.ignore_session_synchronization_failures</literal>
1066+
</entry>
1067+
<entry>
1068+
Whether session synchronisation failures occuring during finalizations of system transaction should be
1069+
ignored or not. <literal>false</literal> by default.
1070+
<para>
1071+
When a system transaction terminates abnormaly, especially through timeouts, it may have its
1072+
completion events running on concurrent threads while the session is still performing some processing.
1073+
To prevent threading concurrency failures, NHibernate then wait for the session to end its processing,
1074+
up to <literal>transaction.system_completion_lock_timeout</literal>. If the session processing is still ongoing
1075+
afterwards, it will by default log an error, perform transaction finalization processing concurrently,
1076+
then throw a synchronization error. This setting allows to disable that later throw.
1077+
</para>
1078+
<para>
1079+
Disabling the throw can be useful if the used data provider has its own locking mechanism applied
1080+
during transaction completion, preventing the session to end its processing. It may then be safe to
1081+
ignore this synchronization failure. In case of threading concurrency failure, you may then need to
1082+
raise <literal>transaction.system_completion_lock_timeout</literal>.
1083+
</para>
1084+
<para>
1085+
<emphasis role="strong">eg.</emphasis>
1086+
<literal>true</literal> | <literal>false</literal>
1087+
</para>
1088+
</entry>
1089+
</row>
10631090
<row>
10641091
<entry>
10651092
<literal>transaction.auto_join</literal>

psake.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Task Set-Configuration {
2929
'connection.connection_string' = 'Server=(local)\SQL2017;Uid=sa;Pwd=Password12!;Database=nhibernateOdbc;Driver={SQL Server Native Client 11.0};Mars_Connection=yes;';
3030
'connection.driver_class' = 'NHibernate.Driver.OdbcDriver';
3131
'odbc.explicit_datetime_scale' = '3';
32+
'transaction.ignore_session_synchronization_failures' = 'true';
33+
'transaction.system_completion_lock_timeout' = '200';
3234
<# We need to use a dialect that avoids mapping DbType.Time to TIME on MSSQL. On modern SQL Server
3335
this becomes TIME(7). Later, such values cannot be read back over ODBC. The
3436
error we get is "System.ArgumentException : Unknown SQL type - SS_TIME_EX.". I don't know for certain

src/NHibernate.Config.Templates/SapSQLAnywhere.cfg.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ for your own use before compiling tests in Visual Studio.
1717
</property>
1818
<property name="dialect">NHibernate.Dialect.SybaseSQLAnywhere12Dialect</property>
1919
<property name="query.substitutions">true=1;false=0</property>
20+
<property name="transaction.ignore_session_synchronization_failures">true</property>
21+
<property name="transaction.system_completion_lock_timeout">200</property>
2022
</session-factory>
2123
</hibernate-configuration>

src/NHibernate.Test/SystemTransactions/SystemTransactionFixture.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -650,8 +650,9 @@ public void CanUseSessionWithManyDependentTransaction(bool explicitFlush)
650650
public void SupportsTransactionTimeout()
651651
{
652652
Assume.That(TestDialect.SupportsTransactionScopeTimeouts, Is.True, "The tested dialect is not supported for transaction scope timeouts.");
653-
// ODBC always freezes the session during transaction scopes timeouts.
654-
Assume.That(Sfi.ConnectionProvider.Driver, Is.Not.InstanceOf(typeof(OdbcDriver)), "ODBC is not supported for transaction scope timeouts.");
653+
// Other special cases: ODBC succeeds this test only with transaction.ignore_session_synchronization_failures enabled.
654+
// It freezes the session during the transaction cancellation. To avoid the test to be very long, the synchronization
655+
// lock timeout should be lowered too.
655656

656657
// Test case adapted from https://github.com/kaksmet/NHibBugRepro
657658

src/NHibernate.Test/TestDialects/SapSQLAnywhere17TestDialect.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,5 @@ public SapSQLAnywhere17TestDialect(Dialect.Dialect dialect)
4343
/// Does not support SELECT FOR UPDATE
4444
/// </summary>
4545
public override bool SupportsSelectForUpdate => false;
46-
47-
/// <summary>
48-
/// SQL Anywhere freezes on transaction scope timeout occuring on concurrent threads, always causing the
49-
/// synchronization for end of session processing to timeout.
50-
/// </summary>
51-
public override bool SupportsTransactionScopeTimeouts => false;
5246
}
5347
}

src/NHibernate/Cfg/Environment.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,12 +142,29 @@ public static string Version
142142
/// after scope disposal. This occurs when the transaction is distributed.
143143
/// This notably concerns <see cref="ISessionImplementor.AfterTransactionCompletion(bool, ITransaction)"/>.
144144
/// NHibernate protects the session from being concurrently used by the code following the scope disposal
145-
/// with a lock. To prevent any application freeze, this lock has a default timeout of five seconds. If the
146-
/// application appears to require longer (!) running transaction completion events, this setting allows to
145+
/// with a lock. To prevent any application freeze, this lock has a default timeout of one second. If the
146+
/// application appears to require longer running transaction completion events, this setting allows to
147147
/// raise this timeout. <c>-1</c> disables the timeout.</para>
148148
/// </summary>
149149
public const string SystemTransactionCompletionLockTimeout = "transaction.system_completion_lock_timeout";
150150
/// <summary>
151+
/// Whether session synchronisation failures occuring during finalizations of system transaction should be
152+
/// ignored or not. <see langword="false" /> by default.
153+
/// </summary>
154+
/// <remarks>
155+
/// <para>When a system transaction terminates abnormaly, especially through timeouts, it may have its
156+
/// completion events running on concurrent threads while the session is still performing some processing.
157+
/// To prevent threading concurrency failures, NHibernate then wait for the session to end its processing,
158+
/// up to <see cref="SystemTransactionCompletionLockTimeout" />. If the session processing is still ongoing
159+
/// afterwards, it will by default log an error, perform transaction finalization processing concurrently,
160+
/// then throw a synchronization error. This setting allows to disable that later throw.</para>
161+
/// <para>Disabling the throw can be useful if the used data provider has its own locking mechanism applied
162+
/// during transaction completion, preventing the session to end its processing. It may then be safe to
163+
/// ignore this synchronization failure. In case of threading concurrency failure, you may then need to
164+
/// raise <see cref="SystemTransactionCompletionLockTimeout" />.</para>
165+
/// </remarks>
166+
public const string IgnoreSessionSynchronizationFailuresOnSystemTransaction = "transaction.ignore_session_synchronization_failures";
167+
/// <summary>
151168
/// When a system transaction is being prepared, is using connection during this process enabled?
152169
/// Default is <see langword="true"/>, for supporting <see cref="FlushMode.Commit"/> with transaction factories
153170
/// supporting system transactions. But this requires enlisting additional connections, retaining disposed

src/NHibernate/Transaction/AdoNetWithSystemTransactionFactory.cs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ public partial class AdoNetWithSystemTransactionFactory : AdoNetTransactionFacto
2424
/// </summary>
2525
protected int SystemTransactionCompletionLockTimeout { get; private set; }
2626
/// <summary>
27+
/// See <see cref="Cfg.Environment.IgnoreSessionSynchronizationFailuresOnSystemTransaction"/>.
28+
/// </summary>
29+
protected bool IgnoreSessionSynchronizationFailuresOnSystemTransaction { get; private set; }
30+
/// <summary>
2731
/// See <see cref="Cfg.Environment.UseConnectionOnSystemTransactionPrepare"/>.
2832
/// </summary>
2933
protected bool UseConnectionOnSystemTransactionPrepare { get; private set; }
@@ -33,10 +37,12 @@ public override void Configure(IDictionary<string, string> props)
3337
{
3438
base.Configure(props);
3539
SystemTransactionCompletionLockTimeout =
36-
PropertiesHelper.GetInt32(Cfg.Environment.SystemTransactionCompletionLockTimeout, props, 5000);
40+
PropertiesHelper.GetInt32(Cfg.Environment.SystemTransactionCompletionLockTimeout, props, 1000);
3741
if (SystemTransactionCompletionLockTimeout < -1)
3842
throw new HibernateException(
3943
$"Invalid {Cfg.Environment.SystemTransactionCompletionLockTimeout} value: {SystemTransactionCompletionLockTimeout}. It can not be less than -1.");
44+
IgnoreSessionSynchronizationFailuresOnSystemTransaction =
45+
PropertiesHelper.GetBoolean(Cfg.Environment.IgnoreSessionSynchronizationFailuresOnSystemTransaction, props, true);
4046
UseConnectionOnSystemTransactionPrepare =
4147
PropertiesHelper.GetBoolean(Cfg.Environment.UseConnectionOnSystemTransactionPrepare, props, true);
4248
}
@@ -130,7 +136,7 @@ protected virtual ITransactionContext CreateAndEnlistMainContext(
130136
{
131137
var transactionContext = new SystemTransactionContext(
132138
session, transaction, SystemTransactionCompletionLockTimeout,
133-
UseConnectionOnSystemTransactionPrepare);
139+
UseConnectionOnSystemTransactionPrepare, IgnoreSessionSynchronizationFailuresOnSystemTransaction);
134140
transactionContext.EnlistedTransaction.EnlistVolatile(
135141
transactionContext,
136142
UseConnectionOnSystemTransactionPrepare
@@ -189,6 +195,7 @@ public class SystemTransactionContext : ITransactionContext, IEnlistmentNotifica
189195

190196
private readonly ISessionImplementor _session;
191197
private readonly bool _useConnectionOnSystemTransactionPrepare;
198+
private readonly bool _ignoreSessionSynchronizationFailures;
192199
private readonly System.Transactions.Transaction _originalTransaction;
193200
private readonly ManualResetEventSlim _lock = new ManualResetEventSlim(true);
194201
private volatile bool _needCompletionLocking = true;
@@ -204,6 +211,8 @@ public class SystemTransactionContext : ITransactionContext, IEnlistmentNotifica
204211
/// <param name="transaction">The transaction into which the context will be enlisted.</param>
205212
/// <param name="systemTransactionCompletionLockTimeout">See <see cref="Cfg.Environment.SystemTransactionCompletionLockTimeout"/>.</param>
206213
/// <param name="useConnectionOnSystemTransactionPrepare">See <see cref="Cfg.Environment.UseConnectionOnSystemTransactionPrepare"/>.</param>
214+
// Since 5.6
215+
[Obsolete("Use overload with an additionnal boolean parameter")]
207216
public SystemTransactionContext(
208217
ISessionImplementor session,
209218
System.Transactions.Transaction transaction,
@@ -217,6 +226,29 @@ public SystemTransactionContext(
217226
_useConnectionOnSystemTransactionPrepare = useConnectionOnSystemTransactionPrepare;
218227
}
219228

229+
/// <summary>
230+
/// Default constructor.
231+
/// </summary>
232+
/// <param name="session">The session to enlist with the transaction.</param>
233+
/// <param name="transaction">The transaction into which the context will be enlisted.</param>
234+
/// <param name="systemTransactionCompletionLockTimeout">See <see cref="Cfg.Environment.SystemTransactionCompletionLockTimeout"/>.</param>
235+
/// <param name="useConnectionOnSystemTransactionPrepare">See <see cref="Cfg.Environment.UseConnectionOnSystemTransactionPrepare"/>.</param>
236+
/// <param name="ignoreSessionSynchronizationFailures">See <see cref="Cfg.Environment.IgnoreSessionSynchronizationFailuresOnSystemTransaction"/>.</param>
237+
public SystemTransactionContext(
238+
ISessionImplementor session,
239+
System.Transactions.Transaction transaction,
240+
int systemTransactionCompletionLockTimeout,
241+
bool useConnectionOnSystemTransactionPrepare,
242+
bool ignoreSessionSynchronizationFailures)
243+
{
244+
_session = session ?? throw new ArgumentNullException(nameof(session));
245+
_originalTransaction = transaction ?? throw new ArgumentNullException(nameof(transaction));
246+
EnlistedTransaction = transaction.Clone();
247+
_systemTransactionCompletionLockTimeout = systemTransactionCompletionLockTimeout;
248+
_useConnectionOnSystemTransactionPrepare = useConnectionOnSystemTransactionPrepare;
249+
_ignoreSessionSynchronizationFailures = ignoreSessionSynchronizationFailures;
250+
}
251+
220252
/// <inheritdoc />
221253
public virtual void Wait()
222254
{
@@ -524,14 +556,16 @@ protected virtual void CompleteTransaction(bool isCommitted)
524556
Dispose();
525557
}
526558

527-
if (isSessionProcessing)
559+
if (isSessionProcessing && !_ignoreSessionSynchronizationFailures)
528560
{
529561
throw new HibernateException(
530562
"A synchronization timeout occurred at transaction completion: the session was still processing. " +
531563
$"You may raise {Cfg.Environment.SystemTransactionCompletionLockTimeout} if it is set too low. " +
532564
"It may also be a limitation of the data provider, " +
533565
"like locks applied on its side while processing transaction cancellations occurring on concurrent threads, " +
534-
"thus preventing the session to finish its current processing during a transaction cancellation.");
566+
"thus preventing the session to finish its current processing during a transaction cancellation. " +
567+
$"In such case, you may enable {Cfg.Environment.IgnoreSessionSynchronizationFailuresOnSystemTransaction}, " +
568+
$"and possibly lower {Cfg.Environment.SystemTransactionCompletionLockTimeout}.");
535569
}
536570
}
537571

src/NHibernate/nhibernate-configuration.xsd

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,32 @@
233233
after scope disposal. This occurs when the transaction is distributed.
234234
This notably concerns ISessionImplementor.AfterTransactionCompletion(bool, ITransaction).
235235
NHibernate protects the session from being concurrently used by the code following the scope disposal
236-
with a lock. To prevent any application freeze, this lock has a default timeout of five seconds. If the
237-
application appears to require longer (!) running transaction completion events, this setting allows to
236+
with a lock. To prevent any application freeze, this lock has a default timeout of one second. If the
237+
application appears to require longer running transaction completion events, this setting allows to
238238
raise this timeout. -1 disables the timeout.
239239
</xs:documentation>
240240
</xs:annotation>
241241
</xs:enumeration>
242+
<xs:enumeration value="transaction.ignore_session_synchronization_failures">
243+
<xs:annotation>
244+
<xs:documentation>
245+
Whether session synchronisation failures occuring during finalizations of system transaction should be
246+
ignored or not. false by default.
247+
248+
When a system transaction terminates abnormaly, especially through timeouts, it may have its
249+
completion events running on concurrent threads while the session is still performing some processing.
250+
To prevent threading concurrency failures, NHibernate then wait for the session to end its processing,
251+
up to transaction.system_completion_lock_timeout. If the session processing is still ongoing
252+
afterwards, it will by default log an error, perform transaction finalization processing concurrently,
253+
then throw a synchronization error. This setting allows to disable that later throw.
254+
255+
Disabling the throw can be useful if the used data provider has its own locking mechanism applied
256+
during transaction completion, preventing the session to end its processing. It may then be safe to
257+
ignore this synchronization failure. In case of threading concurrency failure, you may then need to
258+
raise transaction.system_completion_lock_timeout.
259+
</xs:documentation>
260+
</xs:annotation>
261+
</xs:enumeration>
242262
<xs:enumeration value="transaction.use_connection_on_system_prepare">
243263
<xs:annotation>
244264
<xs:documentation>

teamcity.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
<property name="db-service" value="MSSQL$SQLEXPRESS" />
5151
<property name="nhibernate.connection.driver_class" value="NHibernate.Driver.OdbcDriver" />
5252
<property name="nhibernate.odbc.explicit_datetime_scale" value="3" />
53+
<property name="nhibernate.transaction.ignore_session_synchronization_failures" value="true" />
54+
<property name="nhibernate.transaction.system_completion_lock_timeout" value="200" />
5355
<!-- We need to use a dialect that avoids mapping DbType.Time to TIME on MSSQL. On modern SQL Server
5456
this becomes TIME(7). Later, such values cannot be read back over ODBC. The
5557
error we get is "System.ArgumentException : Unknown SQL type - SS_TIME_EX.". I don't know for certain

0 commit comments

Comments
 (0)