NHibernate concurrent update issue - c#

I’m currently testing concurrent update situations for my c#/NHibernate application.
In the hibernate-configuration command_timeout is set 1 second for testing purposes.
I use the hibernate version in the mapping file.
Here is the code for testing:
namespace Test
{
class Program
{
static void Main(string[] args)
{
MyHibernateConnector hc = new MyHibernateConnector(); // 1 usage see below; also provides some other hibernate related methods
MyHibernateConfig myhibconfig = new MyHibernateConfig(); // reads and holds hibernate configuration
hc.setHibernateConfig(myhibconfig);
ISession session = hc.getSessionAndStartTransaction();
// getSessionAndStartTransaction() does the following:
// - on first call: reads the hibernate configuration and builds the SessionFactory
// - gets the session as follows:
// ISession session;
// if (CurrentSessionContext.HasBind(sf))
// {
// session = sf.GetCurrentSession();
// }
// else
// {
// session = sf.OpenSession();
// CurrentSessionContext.Bind(session);
// }
// - and does session.BeginTransaction();
MyClass obj;
IQuery q = session.CreateQuery("select mc from MyClass as mc where mc.ID = ?");
q.SetInt64(0, 60);
l = q.List<MyClass>();
if (l.Count > 0)
obj = l[0];
session.Transaction.Rollback();
// now update obj in another application with autocommit off and do not commit or rollback
/* breakpont is set here */ session = hc.getSessionAndStartTransaction();
try
{
session.Lock(obj, LockMode.Upgrade);
} catch (Exception e)
{
try
{
session.Transaction.Rollback();
}
catch (Exception e2)
{
Type t = e2.GetType();
}
}
// ...
}
}
}
On session.Lock(obj, LockMode.Upgrade) an exception (GenericADOException) is thrown as expected with message:
could not lock: … for update
If I now catch this exception and try to do an ISession.Transaction.Rollback() a TransactionException is thrown.
I would have expected that a rollback is the appropriate action to resolve concurrent update situations. Is this not so ? And what would be the appropriate action ? What is the state of the transaction after the TransactionException ?
I use NHibernate version 5.1.0.0 and MySql.Data version 6.10.7.0. Hibernate property dialect is NHibernate.Dialect.MySQL55InnoDBDialect.

After some more experimenting I think I found a solution - or should I better say a workaround:
The ISession.Connection is closed after timeout occured on session.Lock(obj, LockMode.Upgrade), so open it again before the rollback:
if (session.Connection.State.Equals(ConnectionState.Closed)) // <- !!!
session.Connection.Open();
session.Transaction.Rollback();
This works and I could do further transactions with the same session.
Why the connection is closed in the first place remains a mystery to me!
And I wonder if this behaviour is common to other (all?) underlying DBMS and/or drivers.

Related

What is wrong with that optimistic concurrency worker implementation?

I have tried to implement an optimistic concurrency 'worker'.
Goal is to read a batch of data from the same database table (single table with no relations) with multiple parallel 'worker'. This did seem to work so far. I get optimistic concurrency exceptions here and there, catch them and retry.
So far so good, and the function to get the data works stable on my local setup. When moving the application to a test environment however, I get a strange timeout exception, which even if caught, will end the async function (breaks the while loop). Does someone see a flaw in the implementation? What could cause the timeout? What could cause the end of the async function?
public async IAsyncEnumerable<List<WorkItem>> LoadBatchedWorkload([EnumeratorCancellation] CancellationToken token, int batchSize, int runID)
{
DataContext context = null;
try
{
context = GetNewContext(); // create a new dbContext
List<WorkItem> workItems;
bool loadSuccessInner;
while (true)
{
if (token.IsCancellationRequested) break;
loadSuccessInner = false;
context.Dispose();
context = GetNewContext(); // create a new dbContext
RunState currentRunState = context.Runs.Where(a => a.Id == runID).First().Status;
try
{
// Error happens on the following line: Microsoft.Data.SqlClient.SqlException: Timeout
workItems = context.WorkItems.Where(a => a.State == ProcessState.ToProcess).Take(batchSize).ToList();
loadSuccessInner = true;
}
catch (Exception ex)
{
workItems = new List<WorkItem>();
}
if (workItems.Count == 0 && loadSuccessInner)
{
break;
}
//... update to a different RunState
//... if set successful yield the result
//... else cleanup and retry
}
}
finally
{
if (context != null) context.Dispose();
}
}
I verified that EntityFramework (here with MS SQL Server adapter) does full server side query, which
translates to a simple query like this: SELECT TOP 10 field_1, field_2 FROM WorkItems WHERE field_2 = 0
The query usually takes <1ms and the timeout is left on default of
30s
I verified that there are no cancelation requests fired
This happens also when there is only a single worker and no one else is accessing the database. I'm aware that a timeout can happen when the resource is busy or blocked. But until now, I never saw a timeout on any other query yet.
(I'll update this answer whenever more information is being provided.)
Does someone see a flaw in the implementation?
Generally, your code looks fine.
What could cause the end of the async function?
Nothing in the code you showed should normally be an issue. Start by putting another try-catch block inside the loop, to ensure, that no other exceptions are getting thrown anywhere else (especially later in the not shown code):
public async IAsyncEnumerable<List<WorkItem>> LoadBatchedWorkload([EnumeratorCancellation] CancellationToken token, int batchSize, int runID)
{
DataContext context = null;
try
{
context = GetNewContext();
List<WorkItem> workItems;
bool loadSuccessInner;
while (true)
{
try
{
// ... (the inner loop code)
}
catch (Exception e)
{
// TODO: Log the exception here using your favorite method.
throw;
}
}
}
finally
{
if (context != null) context.Dispose();
}
}
Take a look at your log and ensure, that the log does not show any exceptions being thrown. Then additionally log every possible exit condition (break and return) from the loop, to find out how and why the code exits the loop.
If there are no other break or return statements in your code, then the only way the code can exit from the loop is if zero workItems are successfully returned from the database.
What could cause the timeout?
Make sure, that any Task returning/async methods you call are being called using await.
To track down, where the exceptions are actually coming from, you should deploy a Debug release with pdb files to get a full stack trace with source code line references.
You can also implement a DbCommandInterceptor and trace failing commands on your own:
public class TracingCommandInterceptor : DbCommandInterceptor
{
public override void CommandFailed(DbCommand command, CommandErrorEventData eventData)
{
LogException(eventData);
}
public override Task CommandFailedAsync(DbCommand command, CommandErrorEventData eventData, CancellationToken cancellationToken = new CancellationToken())
{
LogException(eventData);
return Task.CompletedTask;
}
private static void LogException(CommandErrorEventData eventData)
{
if (eventData.Exception is SqlException sqlException)
{
// -2 = Timeout error
// See https://learn.microsoft.com/en-us/previous-versions/sql/sql-server-2008-r2/cc645611(v=sql.105)?redirectedfrom=MSDN
if (sqlException.Number == -2)
{
var stackTrace = new StackTrace();
var stackTraceText = stackTrace.ToString();
// TODO: Do some logging here and output the stackTraceText
// and other helpful information like the command text etc.
// -->
}
}
}
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(LoggingFactory);
optionsBuilder.UseSqlServer(connectionString);
optionsBuilder.EnableSensitiveDataLogging();
optionsBuilder.EnableDetailedErrors();
// Add the command interceptor.
optionsBuilder.AddInterceptors(new TracingCommandInterceptor());
base.OnConfiguring(optionsBuilder);
}
Additionally logging the command text of the failed command in the interceptor is also a good idea.

Using System.Transaction how to update multiple rows in Entity Framework

I want to use System.Transactions and update multiple rows. My database is connected using Entity Framework.
Below is the code I tried but it throws an error :
public void Update(List<PortfolioCompanyLinkModel> record)
{
var transaction = _context.Database.BeginTransaction();
try
{
foreach (var item in record)
{
var portfolioCompanyLink = _context.PortfolioCompanyLink.FirstOrDefault(p => p.Id == item.Id);
portfolioCompanyLink.ModifiedBy = _loggedInUser;
portfolioCompanyLink.ModifiedOn = DateTime.UtcNow;
portfolioCompanyLink.URL = item.URL;
_context.SaveChanges();
//_context.PortfolioCompanyLink.Update(portfolioCompanyLink);
}
transaction.Commit();
}
catch(Exception ex)
{
transaction.Rollback();
}
}
Error:
The configured execution strategy 'SqlServerRetryingExecutionStrategy' does not support user initiated transactions. Use the execution strategy returned by 'DbContext.Database.CreateExecutionStrategy()' to execute all the operations in the transaction as a retriable unit.
Can someone help me on how to proceed with this?
You problem is the SqlServerRetryingExecutionStrategy as described in Microsoft documentation
When not using a retrying execution strategy you can wrap multiple operations in a single transaction. For example, the following code wraps two SaveChanges calls in a single transaction. If any part of either operation fails then none of the changes are applied.
MS docs on resiliency
System.InvalidOperationException: The configured execution strategy 'SqlServerRetryingExecutionStrategy' does not support user initiated transactions. Use the execution strategy returned by 'DbContext.Database.CreateExecutionStrategy()' to execute all the operations in the transaction as a retriable unit.
Solution: Manually Call Execution Strategy
var executionStrategy = _context.db.CreateExecutionStrategy();
executionStrategy.Execute(
() =>
{
// execute your logic here
using(var transaction = _context.Database.BeginTransaction())
{
try
{
foreach (var item in record)
{
var portfolioCompanyLink = _context.PortfolioCompanyLink.FirstOrDefault(p => p.Id == item.Id);
portfolioCompanyLink.ModifiedBy = _loggedInUser;
portfolioCompanyLink.ModifiedOn = DateTime.UtcNow;
portfolioCompanyLink.URL = item.URL;
_context.SaveChanges();
//_context.PortfolioCompanyLink.Update(portfolioCompanyLink);
}
transaction.Commit();
}
catch(Exception ex) {
transaction.Rollback();
}
}
});
You can set the strategy globally too, but that depends on what you are trying to achieve.

C# transaction with handled exception will still roll back?

Considering this piece of code:
using(TransactionScope tran = new TransactionScope()) {
insertStatementMethod1();
insertStatementMethod2();
// this might fail
try {
insertStatementMethod3();
} catch (Exception e) {
// nothing to do
}
tran.Complete();
}
Is anything done in insertStatementMethod1 and insertStatementMethod2 going to be rolled back? In any case?
If I want them to execute anyway, I would need to check if it insertStatementMethod3 will fail before the transaction, and build my transaction code based on that?
Update
The code looks similar to this
using(TransactionScope tran = new TransactionScope()) {
// <standard code>
yourExtraCode();
// <standard code>
tran.Complete();
}
where I get to write the yourExtraCode() method
public void yourExtraCode() {
insertStatementMethod1();
insertStatementMethod2();
// this call might fail
insertStatementMethod3();
}
I can only edit the yourExtraCode() method, so I cannot chose to be in the transaction scope or no. One simple possible solution would be this:
public void yourExtraCode() {
insertStatementMethod1();
insertStatementMethod2();
// this call might fail
if (findOutIfIcanInsert()) { // <-- this would come by executing sql query
try {
insertStatementMethod3();
} catch (Exception e) {
// nothing to do
}
}
}
But that would come with the need of looking up things in the db which would affect performance.
Is there a better way, or I need to find out before I'd call the method?
I tried out and, of course the transaction was rolled back as expected.
If you don't want your first two methods to be transacted, just move them out from the ambient transaction's scope.
If you don't have control over the code which starts an ambient transaction, you can suppress it by creating a new ambient transaction: using (var scope = new TransactionScope(TransactionScopeOption.Suppress)).

Design pattern with prolog and epilog

I'm searching for design patter that could implement some prolog code and then epilog code.
Let me explain:
I have an function (a lot of them) that amost do the same thing:
this is presudo code but actually it's written in C# 4.5
public IDatabaseError GetUserByName(string Name)
{
try
{
//Initialize session to database
}
catch (Exception)
{
// return error with description for this step
}
try
{
// Try to create 'transaction' object
}
catch(Exception)
{
// return error with description about this step
}
try
{
// Execute call to database with session and transaction object
//
// Actually in all function only this section of the code is different
//
}
catch(Exception)
{
// Transaction object rollback
// Return error with description for this step
}
finally
{
// Close session to database
}
return everything-is-ok
}
So - as you can see 'prolog' (Create session, transaction, other helper function) and 'epilog' (close session, rollback transaction, clean memeory, etc..) is the same for all functions.
Some restrictions:
I want to keep session and transaction object creation/destruction process in function and not in ctor
Custom code (that running in the middle) must be wrapped in try/catch and return different error for different situation
I'm open for any Func<>, Action<> preferable Task<> functions suggestions
Any ideas for design patter or code refactoring ?
This can be achieved by using IDisposable objects as for example:
using(var uow = new UnitOfWork() )
using(var t = new TransactionScope() )
{
//query the database and throws exceptions
// in case of errors
}
Please nothe the TransactionScope class is an out-of-the box class you have in System.Transaction that works ( not only ) with DB connections.
In the UnitOfWork constructor do the "Prologue" code ( ie open the connection... ), in the Dispose do the epilogue part. By throwing exception when error occours you are sure the epilogue part is called anyway.
It sounds like you're looking for the Template Method Pattern.
The template method pattern will allow you to reduce the amount of duplicated code in similar methods by extracting out only the parts of the method which are different.
For this particular example, you could write a method that does all the grunt work, and then invokes a callback to do the interesting work...
// THIS PART ONLY WRITTEN ONCE
public class Database
{
// This is the template method - it only needs to be written once, so the prolog and epilog only exist in this method...
public static IDatabaseError ExecuteQuery(Action<ISession> queryCallback)
{
try
{
//Initialize session to database
}
catch (Exception)
{
// return error with description for this step
}
try
{
// Try to create 'transaction' object
}
catch(Exception)
{
// return error with description about this step
}
try
{
// Execute call to database with session and transaction object
//
// Actually in all function only this section of the code is different
//
var session = the session which was set up at the start of this method...
queryCallback(session);
}
catch(Exception)
{
// Transaction object rollback
// Return error with description for this step
}
finally
{
// Close session to database
}
return everything-is-ok
}
}
This is the usage:
// THIS PART WRITTEN MANY TIMES
IDatabaseError error = Database.ExecuteQuery(session =>
{
// do your unique thing with the database here - no need to write the prolog / epilog...
// you can use the session variable - it was set up by the template method...
// you can throw an exception, it will be converted to IDatabaseError by the template method...
});
if (error != null)
// something bad happened!
I hope I have explained better this time :)

How to in case of timeout to execute method again and again until it completes successfully?

I have asp.net application. All business logic in business layer.
Here is the example of the method
public void DoSomething()
{
PersonClass pc = new PersonClass();
pc.CreatePerson();
pc.AssignBasicTask();
pc.ChangePersonsStatus();
pc.CreateDefaultSettings();
}
what happens once in a while, one of the sub method can timeout, so as a result the process can be incompleted.
what I think in this case to make sure all steps completed properly is
public void DoSomething()
{
PersonClass pc = new PersonClass();
var error = null;
error = pc.CreatePerson();
if(error != timeout exception)
error = pc.AssignBasicTask();
else
return to step above
if(error != timeout exception)
error = pc.ChangePersonsStatus();
else
return to step above
if(error != timeout exception)
error = pc.CreateDefaultSettings();
else
return to step above
}
but it's just an idea, more then sure it's a proper way how to handle this.
Of course, this can be done more or less elegantly, with different options for timing out or giving up - but an easy way to achieve what you want, would be to define a retry method which keeps retrying an action until it succeeds:
public static class RetryUtility
{
public T RetryUntilSuccess<T>(Func<T> action)
{
while(true)
{
try
{
return action();
}
catch
{
// Swallowing exceptions is BAD, BAD, BAD. You should AT LEAST log it.
}
}
}
public void RetryUntilSuccess(Action action)
{
// Trick to allow a void method being passed in without duplicating the implementation.
RetryUntilSuccess(() => { action(); return true; });
}
}
Then do
RetryUtility.RetryUntilSuccess(() => pc.CreatePerson());
RetryUtility.RetryUntilSuccess(() => pc.AssignBasicTask());
RetryUtility.RetryUntilSuccess(() => pc.ChangePersonsStatus());
RetryUtility.RetryUntilSuccess(() => pc.CreateDefaultSettings());
I must urge you to think about what to do if the method keeps failing, you could be creating an infinite loop - perhaps it should give up after N retries or back off with exponentially raising retry time - you will need to define that, since we cannot know enough about your problem domain to decide that.
You have it pretty close to correct in your psuedo-code, and there a lot of ways to do this, but here is how I would do it:
PersonClass pc = new PersonClass();
while(true)
if(pc.CreatePerson())
break;
while(true)
if(pc.AssignBasicTask())
break;
This assumes that your methods return true to indicate success, false to indicate a timeoiut failure (and probably an exception for any other kind of failure). And while I didn't do it here, I would strongly recommend some sort of try counting to make sure it doesn't just loop forever and ever.
Use a TransactionScope for to make sure everything is executed as a unit. More info here: Implementing an Implicit Transaction using Transaction Scope
You should never retry a timed out operation infinitely, you may end up hanging the server or with an infinite loop or both. There should always be a threshold of how many retries is acceptable to attempt before quitting.
Sample:
using(TransactionScope scope = new TransactionScope())
{
try
{
// Your code here
// If no errors were thrown commit your transaction
scope.Complete();
}
catch
{
// Some error handling
}
}

Categories

Resources