ObjectDisposedException thrown when disposing TransactionScope after module updated

Topics: Customizing Orchard, Troubleshooting
Sep 17, 2012 at 12:12 AM
Edited Sep 17, 2012 at 12:34 AM

I've setup a commerce module based on skywalker's webshop tutorial and I've noticed that when modifying the module (or any other module), after saving changes I get an ObjectDisposedException thrown from the Dispose method of the TransactionManager when it attempts to call _scope.Complete(). It seems this TransactionScope is already disposed. I suspect this is something to do with autofac disposing the the TransactionScope?

Once this occurs, all sites (I'm multi-tenanted) go down and respond with an exception until I iisreset (or reset the app pool).

I'm running Orchard 1.5.1 and not really sure how to track down what is causing this. Just wondering if anyone has seen this kind of behaviour, and if there are some known gotchas I should be watching for?

Could it be caused by the way I've defined or implemented service interfaces within my module? All interfaces I've defined are based on IDependency.

To avoid this issue, I've wrapped the call to _scope.Complete() in the TransactionManager.Dispose() method with a try {} catch (ObjectDisposedException) {} (and swallow the exception). Is there any risk in doing this? I figure if the exception is going to bork the transaction from completing, than I might as well catch it here to prevent me from needing to reset the App Pool.

Initial Error Thrown Is:

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

Stack Trace:

[ObjectDisposedException: Cannot access a disposed object. Object name: 'TransactionScope'.] 
System.Transactions.TransactionScope.Complete() +204566 
Orchard.Data.TransactionManager.System.IDisposable.Dispose() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Data\TransactionManager.cs:64 
Autofac.Core.Disposer.Dispose(Boolean disposing) +82 Autofac.Util.Disposable.Dispose() +43 
Autofac.Core.Lifetime.LifetimeScope.Dispose(Boolean disposing) +86 
Autofac.Util.Disposable.Dispose() +43 
Orchard.Environment.DefaultOrchardHost.DisposeShellContext() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:204 
Orchard.Environment.DefaultOrchardHost.b__6(AcquireContext`1 ctx) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:189 
Orchard.Caching.Cache`2.CreateEntry(TKey k, Func`2 acquire) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\Cache.cs:57 
Orchard.Caching.Cache`2.UpdateEntry(CacheEntry currentEntry, TKey k, Func`2 acquire) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\Cache.cs:33 
Orchard.Caching.<>c__DisplayClass2.b__1(TKey k, CacheEntry currentEntry) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\Cache.cs:21 
System.Collections.Concurrent.ConcurrentDictionary`2.AddOrUpdate(TKey key, Func`2 addValueFactory, Func`3 updateValueFactory) +118 
Orchard.Caching.Cache`2.Get(TKey key, Func`2 acquire) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\Cache.cs:17 Orchard.Caching.DefaultCacheManager.Get(TKey key, Func`2 acquire) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\DefaultCacheManager.cs:33 Orchard.Environment.DefaultOrchardHost.MonitorExtensions() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:185 Orchard.Environment.DefaultOrchardHost.BeginRequest() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:213 Orchard.Environment.DefaultOrchardHost.Orchard.Environment.IOrchardHost.BeginRequest() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:78 Orchard.Web.MvcApplication.HostBeginRequest(HttpApplication application, IOrchardHost host) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard.Web\Global.asax.cs:38 Orchard.WarmupStarter.Starter`1.OnBeginRequest(HttpApplication application) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard.WarmupStarter\Starter.cs:68 Orchard.Web.MvcApplication.Application_BeginRequest() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard.Web\Global.asax.cs:29 [TargetInvocationException: Exception has been thrown by the target of an invocation.] System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner) +0 
System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner) +152 
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) +396 
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +38 
System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) +35 
System.Web.Util.ArglessEventHandlerProxy.Callback(Object sender, EventArgs e) +74 System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +80 
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +270 
After this, the error thrown by all sites is...

Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.

[ObjectDisposedException: Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it has already been disposed.] Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters) +407 
lambda_method(Closure ) +130 
Orchard.Environment.DefaultOrchardShell.Terminate() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardShell.cs:51 Orchard.Environment.DefaultOrchardHost.DisposeShellContext() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:203 Orchard.Environment.DefaultOrchardHost.b__6(AcquireContext`1 ctx) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:189 Orchard.Caching.Cache`2.CreateEntry(TKey k, Func`2 acquire) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\Cache.cs:57 Orchard.Caching.Cache`2.UpdateEntry(CacheEntry currentEntry, TKey k, Func`2 acquire) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\Cache.cs:33 Orchard.Caching.<>c__DisplayClass2.b__1(TKey k, CacheEntry currentEntry) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\Cache.cs:21 System.Collections.Concurrent.ConcurrentDictionary`2.AddOrUpdate(TKey key, Func`2 addValueFactory, Func`3 updateValueFactory) +118 
Orchard.Caching.Cache`2.Get(TKey key, Func`2 acquire) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\Cache.cs:17 Orchard.Caching.DefaultCacheManager.Get(TKey key, Func`2 acquire) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Caching\DefaultCacheManager.cs:33 Orchard.Environment.DefaultOrchardHost.MonitorExtensions() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:185 Orchard.Environment.DefaultOrchardHost.BeginRequest() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:213 Orchard.Environment.DefaultOrchardHost.Orchard.Environment.IOrchardHost.BeginRequest() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard\Environment\DefaultOrchardHost.cs:78 Orchard.Web.MvcApplication.HostBeginRequest(HttpApplication application, IOrchardHost host) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard.Web\Global.asax.cs:38 Orchard.WarmupStarter.Starter`1.OnBeginRequest(HttpApplication application) in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard.WarmupStarter\Starter.cs:68 Orchard.Web.MvcApplication.Application_BeginRequest() in C:\Users\Michael Yates\Documents\Visual Studio 2010\Projects\Orchard.Source.1.5.1\src\Orchard.Web\Global.asax.cs:29 [TargetInvocationException: Exception has been thrown by the target of an invocation.] System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeType typeOwner) +0 
System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo method, Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner) +152 
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) +396 
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) +38 
System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) +35 
System.Web.Util.ArglessEventHandlerProxy.Callback(Object sender, EventArgs e) +74 
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +80 
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +270

Developer
Oct 4, 2012 at 9:49 AM

What exactly did you change? If you setup a new Orchard installation, are you able to reproduce the issue?

Oct 5, 2012 at 2:19 AM

I haven't had a chance to try to reproduce in a clean environment yet. For now I'm silently handling the exception thrown by the call to _scope.Complete() in the dispose method. I figure it's probably best practice to ensure exceptions are not thrown during garbage collection in any case.

I'll let you know how I get on when I have the chance to repro in a clean environment.

Nov 5, 2012 at 4:58 AM

I had a bit more time to investigate this ObjectDisposedException recently after upgrading to Orchard v1.6 and have narrowed it down. I was hoping the upgrade to v1.6 might make the problem go away, but alas no.

That said, the situation has improved somewhat in v1.6. Previously (as mentioned above) the initial exception after changing a module (thus causing the orchard shell to detect the change and restart its shell) came from the call to TransactionScope().Complete() in the TransactionManager().Dispose() method. Then on subsequent page loads (and until manually performing an iisreset) the second exception shown above would occur, effectively leaving all sites down. At least now in Orchard v1.6, the first exception occurs once (after a module is changed), then the second exception is handled and the shell restarts and everything is fine again.

So in my module, I basically removed all my code and incrementally re-added interfaces and classes until the problem re-appeared. I narrowed the problem down to an implementation of ITokenProvider that I had created. In the constructor for my token provider I was passing an instance of ISiteService in so that I could access the site culture and timezone info for the site when evaluating my tokens.

The problem was that instead of storing a reference to the ISiteService then calling _siteService.GetSiteSettings() inside my implementation of the Evaluate() method (of ITokenProvider), I was calling siteService.GetSiteSettings() within the constructor of my token provider class and trying to set private _cultureInfo and _timeZoneInfo members.

It appears that for whatever reason, calling ISiteService.GetSiteSettings() from within the constructor of an ITokenProvider implementation causes some kind of exception at some point which leaves a TransactionScope object (somewhere) in a disposed state, but with it's TransactionManager unaware of this. Thus when going to restart the shell, and the TransactionManager attempts to call _scope.Complete() on the already disposed TransactionScope object, the first exception above is thrown.

I haven't spent a lot of time investigating exactly what the root cause is (ie. why calling ISiteService.GetSiteSettings() from within the constructor of the token provider causes a problem), as the solution for me is to store a reference to ISiteSettings in my token provider, and then call GetSiteSettings() from within my Evaluate() method instead of calling it upfront in the constructor.

I'm wondering are there any conventions about calling (or not calling) methods of IDependency based classes from within the constructors of other IDependency based classes? 

 

Coordinator
Nov 5, 2012 at 6:59 AM

Yeah, you should never do any significant work from a constructor. It's not just a convention, it's good practice. Just get the settings lazily when you need them instead.