Skip to content

Fix some NRE in work isolation and connection handling #2337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 16 additions & 42 deletions src/NHibernate/Async/Transaction/AdoNetTransactionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using NHibernate.Engine;
using NHibernate.Engine.Transaction;
using NHibernate.Exceptions;
using NHibernate.Impl;

namespace NHibernate.Transaction
{
Expand All @@ -42,33 +41,24 @@ async Task InternalExecuteWorkInIsolationAsync()

DbConnection connection = null;
DbTransaction trans = null;
// bool wasAutoCommit = false;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added a small cleanup commit.
Afaik, the auto-commit feature and the transaction manager are not ported into NHibernate, so those decade old comments and todo-s are unlikely to be done any time soon. They are just polluting the code in my view.

try
{
// We make an exception for SQLite and use the session's connection,
// since SQLite only allows one connection to the database.
if (session.Factory.Dialect is SQLiteDialect)
connection = session.Connection;
else
connection = await (session.Factory.ConnectionProvider.GetConnectionAsync(cancellationToken)).ConfigureAwait(false);
connection = session.Factory.Dialect is SQLiteDialect
? session.Connection
: await (session.Factory.ConnectionProvider.GetConnectionAsync(cancellationToken)).ConfigureAwait(false);

if (transacted)
{
trans = connection.BeginTransaction();
// TODO NH: a way to read the autocommit state is needed
//if (TransactionManager.GetAutoCommit(connection))
//{
// wasAutoCommit = true;
// TransactionManager.SetAutoCommit(connection, false);
//}
}

await (work.DoWorkAsync(connection, trans, cancellationToken)).ConfigureAwait(false);

if (transacted)
{
trans.Commit();
//TransactionManager.Commit(connection);
}
}
catch (Exception t)
Expand All @@ -84,49 +74,33 @@ async Task InternalExecuteWorkInIsolationAsync()
}
catch (Exception ignore)
{
isolaterLog.Debug(ignore, "Unable to rollback transaction");
_isolatorLog.Debug(ignore, "Unable to rollback transaction");
}

if (t is HibernateException)
{
throw;
}
else if (t is DbException)
{
throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t,
"error performing isolated work");
}
else
{
throw new HibernateException("error performing isolated work", t);
}
switch (t)
{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another indentation glitch.

case HibernateException _:
throw;
case DbException _:
throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t,
"error performing isolated work");
default:
throw new HibernateException("error performing isolated work", t);
}
}
}
finally
{
//if (transacted && wasAutoCommit)
//{
// try
// {
// // TODO NH: reset autocommit
// // TransactionManager.SetAutoCommit(connection, true);
// }
// catch (Exception)
// {
// log.Debug("was unable to reset connection back to auto-commit");
// }
//}

try
{
trans?.Dispose();
}
catch (Exception ignore)
{
isolaterLog.Warn(ignore, "Unable to dispose transaction");
_isolatorLog.Warn(ignore, "Unable to dispose transaction");
}

if (session.Factory.Dialect is SQLiteDialect == false)
if (connection != null && session.Factory.Dialect is SQLiteDialect == false)
session.Factory.ConnectionProvider.CloseConnection(connection);
}
}
Expand Down
3 changes: 3 additions & 0 deletions src/NHibernate/Connection/ConnectionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public abstract partial class ConnectionProvider : IConnectionProvider
/// <param name="conn">The <see cref="DbConnection"/> to clean up.</param>
public virtual void CloseConnection(DbConnection conn)
{
if (conn == null)
throw new ArgumentNullException(nameof(conn));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can choose to just exit instead of throwing. But since the case was clearly not supported, causing a NRE in the try then a second NRE in the catch, I consider the current code base is never meant to supply null to this method, so it looks better to throw.


log.Debug("Closing connection");
try
{
Expand Down
56 changes: 15 additions & 41 deletions src/NHibernate/Transaction/AdoNetTransactionFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using NHibernate.Engine;
using NHibernate.Engine.Transaction;
using NHibernate.Exceptions;
using NHibernate.Impl;

namespace NHibernate.Transaction
{
Expand All @@ -16,7 +15,7 @@ namespace NHibernate.Transaction
/// </summary>
public partial class AdoNetTransactionFactory : ITransactionFactory
{
private readonly INHibernateLogger isolaterLog = NHibernateLogger.For(typeof(ITransactionFactory));
private static readonly INHibernateLogger _isolatorLog = NHibernateLogger.For(typeof(ITransactionFactory));

/// <inheritdoc />
public virtual ITransaction CreateTransaction(ISessionImplementor session)
Expand Down Expand Up @@ -52,33 +51,24 @@ public virtual void ExecuteWorkInIsolation(ISessionImplementor session, IIsolate

DbConnection connection = null;
DbTransaction trans = null;
// bool wasAutoCommit = false;
try
{
// We make an exception for SQLite and use the session's connection,
// since SQLite only allows one connection to the database.
if (session.Factory.Dialect is SQLiteDialect)
connection = session.Connection;
else
connection = session.Factory.ConnectionProvider.GetConnection();
connection = session.Factory.Dialect is SQLiteDialect
? session.Connection
: session.Factory.ConnectionProvider.GetConnection();

if (transacted)
{
trans = connection.BeginTransaction();
// TODO NH: a way to read the autocommit state is needed
//if (TransactionManager.GetAutoCommit(connection))
//{
// wasAutoCommit = true;
// TransactionManager.SetAutoCommit(connection, false);
//}
}

work.DoWork(connection, trans);

if (transacted)
{
trans.Commit();
//TransactionManager.Commit(connection);
}
}
catch (Exception t)
Expand All @@ -94,49 +84,33 @@ public virtual void ExecuteWorkInIsolation(ISessionImplementor session, IIsolate
}
catch (Exception ignore)
{
isolaterLog.Debug(ignore, "Unable to rollback transaction");
_isolatorLog.Debug(ignore, "Unable to rollback transaction");
}

if (t is HibernateException)
switch (t)
{
throw;
}
else if (t is DbException)
{
throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t,
"error performing isolated work");
}
else
{
throw new HibernateException("error performing isolated work", t);
case HibernateException _:
throw;
case DbException _:
throw ADOExceptionHelper.Convert(session.Factory.SQLExceptionConverter, t,
"error performing isolated work");
default:
throw new HibernateException("error performing isolated work", t);
}
}
}
finally
{
//if (transacted && wasAutoCommit)
//{
// try
// {
// // TODO NH: reset autocommit
// // TransactionManager.SetAutoCommit(connection, true);
// }
// catch (Exception)
// {
// log.Debug("was unable to reset connection back to auto-commit");
// }
//}

try
{
trans?.Dispose();
}
catch (Exception ignore)
{
isolaterLog.Warn(ignore, "Unable to dispose transaction");
_isolatorLog.Warn(ignore, "Unable to dispose transaction");
}

if (session.Factory.Dialect is SQLiteDialect == false)
if (connection != null && session.Factory.Dialect is SQLiteDialect == false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's some messy method here.... Additional null check doesn't make it better. May be instead move connection retrieving logic outside of try block?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well maybe not.. That would change exception..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least this will no more swallow a connection opening failure. But yes, it is indeed better to put the connection opening out of the try. It has no sense being into it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well... Changing from NRE unrelated to the trouble to the bare exception is still better.
But yes, it loses the HibernateException encapsulation.

Switching back.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just realized that this exception never was wrapped (my main concern was that we changed existing behavior - but we didn't) and I really don't see any benefits in such wrappings (seems like some legacy practice). So maybe better again switch back? )

session.Factory.ConnectionProvider.CloseConnection(connection);
}
}
Expand Down