Transactions and PLINQ

Topics: Core, Writing modules
Apr 19, 2012 at 5:57 PM
Edited Apr 19, 2012 at 7:40 PM

I'd like to sum up the issue regarding transaction used inside the threads.

Here is the first mention about it: http://orchard.codeplex.com/discussions/273971

 

 
public void CheckAll(IEnumerable<HttpProxyRecord> records) 
{

    records.AsParallel().WithDegreeOfParallelism(10).ForAll(
        p => {
            using (var context = _workContextAccessor.CreateWorkContextScope()) {
                context.Resolve<IOrchardServices>().TransactionManager.Demand();
                try {
                    var pcs = context.Resolve<IUptimeCheckerService>();
                    pcs.Check(p);
                }
                catch (Exception ex) {
                    Logger.Error(ex, "Error updating IP: " + p.IP);
                }
            }
        });
}

 

Normally, Background service should be used for such updates. But I'm not using Parts, therefore, I don't have content items for my records. In try/catch block there is some synchronization and remove web request (timout 8 seconds) work.
It is surprising, that from 30 records only are 2-5 throwing exception, which tells: System.Data.SqlClient.SqlException: MSDTC on server 'IKUTSIN-VB-WIN7' is unavailable

Apr 19, 2012 at 7:10 PM
Edited Apr 19, 2012 at 7:21 PM

Previous approach doesn't work as a background task. The error is:  TransactionScope has a different IsolationLevel. And TransactionScope nested incorrectly when I useTransactionScopeOption.Suppress.

Developer
Apr 20, 2012 at 1:25 AM

Using PLINQ on all database records (I assume the HttpProxyRecord is one, it doesn't matter if it is a content part record or not) is a bad idea. This error happens, because each record is being bound to a specific NHibernate session/transaction and you are creating a new transaction in each parallel task. That messes things up and is not gonna work. It's not surprising that you see errors at random - each task won't necessarily be called on a separate thread, as scheduler makes that decision at runtime.

I'd rather loop over record ids and then fetch each record and do something with it inside the parallel task - inside the totally separate work context scope.

The previous approach throws the "TransactionScope has a different IsolationLevel", because ITransactionManager.Demand creates a transaction with ReadCommitted option, and you try to create a nested transaction with ReadUncommitted. SQL Server docs say:

"Only one of the isolation level options can be set at a time, and it remains set for that connection until it is explicitly changed."


Apr 20, 2012 at 11:00 AM
Edited Apr 20, 2012 at 11:00 AM

Yes, last night I've finally made it working.

You are absolutelly about entity tracking - so I'm creating an anonymous instance for each entity.

I've also figured out why the transaction had nesing error - Transaction for underlaying repository classes is disposed when container scope finishes it's life.

So the working approach is:

 

records.Select(r=>new{r.IP, r.ProviderName,r.Comment}).ToList()
	.AsParallel().WithDegreeOfParallelism(10)
	.ForAll(
	p =>
	{
		var level = new TransactionOptions();
		level.IsolationLevel = IsolationLevel.ReadCommitted;
		using (var scope = new TransactionScope(TransactionScopeOption.RequiresNew, level))
		{
			using (var context = _workContextAccessor.CreateWorkContextScope())
			{
				try {
					var pcs = context.Resolve<ICheckerService>();
					pcs.CheckProxy(p.IP, p.ProviderName, p.Comment, forceCheck);
					scope.Complete();
				}
				catch (Exception ex) {
					Logger.Error(ex, "Error updating IP: " + p.IP);
				}
			}
		}
	});

 

Generally, the issue is solved. But during the investigations I found that SweepGenrator and BackgroundService create own transaction scopes. This cause the table locking if Background task will be a long running process.

Apr 20, 2012 at 6:48 PM

Well, seems that SweepGenrator and BackgroundService transaction scopes are not very practical. :(

I'm experiencing "Thread abort" exceptions caused by timeouts, when several tasks running in parallel, and use same repository. I'm not sure if there is a workaround for that. Any thoughts?