TransactionScope nested incorrectly

Topics: Troubleshooting, Writing modules
Mar 27, 2012 at 11:24 PM

In my custom Orchard module (for 1.4), I have the need to access an external SQL database. I currently wrap all my data access calls with a TransactionScopeOption.Suppress (as suggested, say, here: http://orchard.codeplex.com/discussions/262662), like so:

public virtual TResult Using<TResult>(Func<IRepository, TResult> action)
{
	using (var conn = new EntityConnection("name=AlantaEntities"))
	{
		using (var repository = new Repository(new AlantaEntities(conn)))
		{
			using (new TransactionScope(TransactionScopeOption.Suppress))
			{
				TResult result = action(repository);
				conn.Close();
				return result;
			}
		}
	}
}

However, I still periodically get the error: "TransactionScope nested incorrectly". This seems to happen mostly the first time I access the database after Orchard spins up. Still, it's not helpful. 

A typical error page looks like this:

TransactionScope nested incorrectly.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: TransactionScope nested incorrectly.

Source Error:

Line 55:                 _cacheContextAccessor.Current = context;
Line 56: 
Line 57:                 entry.Result = acquire(context);
Line 58:             }
Line 59:             finally {


Source File: c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Caching\Cache.cs Line: 57

Stack Trace:

[InvalidOperationException: TransactionScope nested incorrectly.]
   System.Transactions.TransactionScope.Dispose() +86014
   Alanta.Web.Corp.Helpers.RepositoryHelper.Using(Func`2 action) +301
   Alanta.Web.Corp.Services.<>c__DisplayClass5.<GetAuthenticatedUser>b__3(AcquireContext`1 context) +105
   Orchard.Caching.Cache`2.CreateEntry(TKey k, Func`2 acquire) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Caching\Cache.cs:57
   Orchard.Caching.Cache`2.AddEntry(TKey k, Func`2 acquire) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Caching\Cache.cs:27
   Orchard.Caching.<>c__DisplayClass2.<Get>b__0(TKey k) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Caching\Cache.cs:19
   System.Collections.Concurrent.ConcurrentDictionary`2.AddOrUpdate(TKey key, Func`2 addValueFactory, Func`3 updateValueFactory) +212
   Orchard.Caching.Cache`2.Get(TKey key, Func`2 acquire) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Caching\Cache.cs:17
   Orchard.Caching.DefaultCacheManager.Get(TKey key, Func`2 acquire) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Caching\DefaultCacheManager.cs:33
   Alanta.Web.Corp.Services.AlantaAuthenticationService.GetAuthenticatedUser() +598
   Orchard.Security.CurrentUserWorkContext.<Get>b__0(WorkContext ctx) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Security\CurrentUserWorkContext.cs:13
   Orchard.Environment.<>c__DisplayClass7`1.<FindResolverForState>b__5() in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Environment\WorkContextImplementation.cs:37
   Orchard.Environment.WorkContextImplementation.GetState(String name) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Environment\WorkContextImplementation.cs:28
   Orchard.WorkContext.get_CurrentUser() in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\WorkContext.cs:60
   Orchard.Security.Authorizer.Authorize(Permission permission, IContent content, LocalizedString message) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Security\Authorizer.cs:72
   Orchard.Security.Authorizer.Authorize(Permission permission) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Security\Authorizer.cs:60
   Orchard.Security.SecurityFilter.OnAuthorization(AuthorizationContext filterContext) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Security\SecurityFilter.cs:24
   System.Web.Mvc.ControllerActionInvoker.InvokeAuthorizationFilters(ControllerContext controllerContext, IList`1 filters, ActionDescriptor actionDescriptor) +103
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +345
   System.Web.Mvc.Controller.ExecuteCore() +115
   System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext) +94
   System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext) +10
   System.Web.Mvc.<>c__DisplayClassb.<BeginProcessRequest>b__5() +37
   System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +21
   System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _) +12
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +55
   System.Web.Mvc.<>c__DisplayClasse.<EndProcessRequest>b__d() +49
   System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f) +7
   System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action) +23
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +59
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +9
   Orchard.Mvc.Routes.HttpAsyncHandler.EndProcessRequest(IAsyncResult result) in c:\source\Alanta\working\AlantaWebCorp\src\Orchard\Mvc\Routes\ShellRoute.cs:147
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8969201
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184

Any thoughts on troubleshooting this? I've looked through every discussion here that talks about transaction scopes, and haven't been able to find anything definitive.

Coordinator
Mar 27, 2012 at 11:48 PM

If you have a reliable and simple repro, please file a bug.

Apr 3, 2012 at 9:23 PM

I've been trying to come up with a repro, and so far haven't been able to. I've tried creating a separate module that has similar (but very simplified) data access patterns (e.g., through the Enterprise Framework, etc.), but I haven't yet been able to get it to throw the error in question. However, when my actual module is loaded, it seems to repro pretty reliably, i.e., if I restart IIS and then try to sign in, it throws the error. It's difficult to troubleshoot, because any offending code (which is obviously related to my code, presumably the data access portion) isn't actually within the call stack. I'll keep poking around, but any troubleshooting suggestions would be helpful.

Coordinator
Apr 4, 2012 at 3:18 AM

My guess would be that you are trying to use objects in a cache Lambda that are transaction-dependent. Can you show the caching code that you have, and maybe try to comment it out to see if it makes a difference?

Apr 4, 2012 at 5:44 PM

I was only using a cache in two places, and commenting those out still leaves me with the same problem.

For what it's worth, I'm getting a different error most of the time these days. Instead of the "TransactionScope nested incorrectly" error, I'm usually seeing this one:

Cannot access a disposed object.
Object name: 'TransactionScope'.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'TransactionScope'.

Source Error:

Line 41:                 if (!_cancelled) {
Line 42:                     Logger.Debug("Marking transaction as complete");
Line 43:                     _scope.Complete();
Line 44:                 }
Line 45: 


Source File: C:\source\Alanta\working\AlantaWebCorp\src\Orchard\Data\TransactionManager.cs Line: 43

Stack Trace:

[ObjectDisposedException: Cannot access a disposed object.
Object name: 'TransactionScope'.]
   System.Transactions.TransactionScope.Complete() +83955
   Orchard.Data.TransactionManager.System.IDisposable.Dispose() in C:\source\Alanta\working\AlantaWebCorp\src\Orchard\Data\TransactionManager.cs:43
   Autofac.Core.Disposer.Dispose(Boolean disposing) +79
   Autofac.Util.Disposable.Dispose() +46
   Autofac.Core.Lifetime.LifetimeScope.Dispose(Boolean disposing) +57
   Autofac.Util.Disposable.Dispose() +46
   Orchard.Environment.<>c__DisplayClass2.<.ctor>b__0() in C:\source\Alanta\working\AlantaWebCorp\src\Orchard\Environment\WorkContextAccessor.cs:75
   Orchard.Environment.HttpContextScopeImplementation.System.IDisposable.Dispose() in C:\source\Alanta\working\AlantaWebCorp\src\Orchard\Environment\WorkContextAccessor.cs:80
   Orchard.Mvc.Routes.HttpAsyncHandler.EndProcessRequest(IAsyncResult result) in C:\source\Alanta\working\AlantaWebCorp\src\Orchard\Mvc\Routes\ShellRoute.cs:150
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +8969201
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +184

This is with the latest changeset on the 1.x branch. Any chance it might be related to this issue? http://orchard.codeplex.com/workitem/18406

Apr 5, 2012 at 12:42 AM

For what it's worth, I tried commenting out my TransactionScope code, and things seem to be working correctly now, at least for the moment.

public virtual TResult Using<TResult>(Func<IRepository, TResult> action)
{
	using (var conn = new EntityConnection("name=AlantaEntities"))
	{
		using (var repository = new Repository(new AlantaEntities(conn)))
		{
			// using (new TransactionScope(TransactionScopeOption.Suppress))
			{
				TResult result = action(repository);
				return result;
			}
		}
	}
}

I remember that there were specific reasons I put that code in there, so I may run into other problems later on - but this is worth noting, at least.

Apr 5, 2012 at 1:12 AM

I don't know much about how these things work, so this probably won't help, but what if you tried moving the using(new TransactionScope()) {} to become the outermost part of the code inside that method? 

Developer
Apr 5, 2012 at 9:42 AM
Edited Apr 5, 2012 at 9:51 AM

@TheMonarch is right - the error is most probably the effect of incorrect nesting.

The repository-related 'using' (I'm not sure, if the connection-related one too, though) has to be placed inside the transaction scope one . Otherwise it automatically uses Orchard ambient transaction, which gets disposed along with the repository. So when the method finishes, you end up with prematurely disposed Orchard transaction. And in effect the TransactionManager throws an exception when it tries to dispose the underlying transaction at the end of current request (as it's already been disposed).

Apr 7, 2012 at 1:14 AM

I gave that a shot, but unfortunately it still throws the same exception. So far, removing the transaction (i.e., allowing it to participate in Orchard's ambient transaction) is the only solution I've found that works so far. Since I'm not using explicitly using transactions in my own data access (so far at least), I think that'll be a workable solution. If I need to start using my own transactions, well, I'll likely be back here again :-).

Apr 9, 2012 at 8:28 PM

Well, I think I figured out what the issue was. Turns out that within the code that I'd wrapped with a TransactionScopeOption.Suppress, I was creating an Orchard UserPart, which of course ends up doing some Orchard database access, which ends up requiring a separate transaction, and that just messed everything up. I modified my module to make sure that all Orchard DB access happened outside of my own little suppressed transaction, and that seems to have fixed it.