Skip to content
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

Issue with SignalR--scopes appear to get confused #533

Closed
camhart opened this issue Apr 15, 2018 · 1 comment
Closed

Issue with SignalR--scopes appear to get confused #533

camhart opened this issue Apr 15, 2018 · 1 comment
Labels

Comments

@camhart
Copy link

camhart commented Apr 15, 2018

Issue

I'm getting an ObjectDisposedException:

Cannot access a disposed object. Object name: 'SimpleInjector.Scope'.

in my SimpleInjectorHubActivator when attempting:

return (IHub)container.GetInstance(descriptor.HubType);

only when called by OnReceived method from the SimpleInjectorHubDispatcher. For all other calls in the hub dispatcher (OnConnected, OnDisconnected, and OnReconnected) no exception is thrown.

Debugging findings leading to belief scopes are confused

I cloned down the Simple Injector 4.1.x branch and added it to my project so I could debug. Here's is what I've found:

  1. Line 446 of Scope.cs is where the exception is thrown. Here's the call stack at that point:
SimpleInjector.dll!SimpleInjector.Scope.RequiresInstanceNotDisposed() Line 446    C#    Symbols loaded.
SimpleInjector.dll!SimpleInjector.Scope.GetInstanceInternal<Scs.Hubs.CaseHub>(SimpleInjector.Lifestyles.ScopedRegistration<Scs.Hubs.CaseHub> registration = {SimpleInjector.Lifestyles.ScopedRegistration<Scs.Hubs.CaseHub>}) Line 378    C#    Symbols loaded.
SimpleInjector.dll!SimpleInjector.Scope.GetInstance<Scs.Hubs.CaseHub>(SimpleInjector.Lifestyles.ScopedRegistration<Scs.Hubs.CaseHub> registration = {SimpleInjector.Lifestyles.ScopedRegistration<Scs.Hubs.CaseHub>}, SimpleInjector.Scope scope = {SimpleInjector.Scope}) Line 246    C#    Symbols loaded.
SimpleInjector.dll!SimpleInjector.Advanced.Internal.LazyScopedRegistration<Scs.Hubs.CaseHub>.GetInstance(SimpleInjector.Scope scope = {SimpleInjector.Scope}) Line 68    C#    Symbols loaded.
[Lightweight Function]        Annotated Frame
SimpleInjector.dll!SimpleInjector.InstanceProducer.GetInstance() Line 257    C#    Symbols loaded.
SimpleInjector.dll!SimpleInjector.Container.GetInstance(System.Type serviceType = {Name = "CaseHub" FullName = "Scs.Hubs.CaseHub"}) Line 102    C#    Symbols loaded.
Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubActivator.Create(Microsoft.AspNet.SignalR.Hubs.HubDescriptor descriptor = {Microsoft.AspNet.SignalR.Hubs.HubDescriptor}) Line 25    C#    Symbols loaded.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Hubs.HubDispatcher.CreateHub(Microsoft.AspNet.SignalR.IRequest request = {Microsoft.AspNet.SignalR.Owin.ServerRequest}, Microsoft.AspNet.SignalR.Hubs.HubDescriptor descriptor = {Microsoft.AspNet.SignalR.Hubs.HubDescriptor}, string connectionId = "3699a11c-bcb2-4df6-bb25-28d86971c069", Microsoft.AspNet.SignalR.Hubs.StateChangeTracker tracker = {Microsoft.AspNet.SignalR.Hubs.StateChangeTracker}, bool throwIfFailedToCreate = true)    Unknown    Non-user code. Skipped loading symbols.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Hubs.HubDispatcher.OnReceived(Microsoft.AspNet.SignalR.IRequest request, string connectionId, string data)    Unknown    Non-user code. Skipped loading symbols.
Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.OnReceived(Microsoft.AspNet.SignalR.IRequest request = {Microsoft.AspNet.SignalR.Owin.ServerRequest}, string connectionId = "3699a11c-bcb2-4df6-bb25-28d86971c069", string data = "{\"H\":\"casehub\",\"M\":\"Init\",\"A\":[\"9021\"],\"I\":0}") Line 37    C#    Symbols loaded.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequestPostGroupRead.AnonymousMethod__5()    Unknown    Non-user code. Skipped loading symbols.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.TaskAsyncHelper.FromMethod(System.Func<System.Threading.Tasks.Task> func)    Unknown    Non-user code. Skipped loading symbols.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Transports.WebSocketTransport.OnMessage(string message)    Unknown    Non-user code. Skipped loading symbols.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler.ProcessWebSocketRequestAsync(System.Net.WebSockets.WebSocket webSocket = {System.Web.WebSockets.AspNetWebSocket}, System.Threading.CancellationToken disconnectToken = IsCancellationRequested = false, System.Func<object, System.Threading.Tasks.Task<Microsoft.AspNet.SignalR.WebSockets.WebSocketMessage>> messageRetriever = {Method = {System.Reflection.RuntimeMethodInfo}}, object state = {Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler.ReceiveContext})    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.OutputAsyncCausalityEvents.AnonymousMethod__0()    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.OutputWaitEtwEvents.AnonymousMethod__0()    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.GetActionLogDelegate.AnonymousMethod__0()    Unknown    Non-user code. Skipped loading symbols.
System.Web.dll!System.Web.Util.SynchronizationHelper.SafeWrapCallback(System.Action action)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.Tasks.Task.Execute()    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot = Id = 27510, Status = Running, Method = "Void <QueueAsynchronous>b__0(System.Threading.Tasks.Task)")    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()    Unknown    Non-user code. Skipped loading symbols.
[Native to Managed Transition]        Annotated Frame
  1. Letting the previous exception bubble up, I see it reach line 37 of SimpleInjectorHubDispatcher just prior to exiting the using statement.

Here's what that looks like:

protected override Task OnReceived(IRequest request, string connectionId, string data)
{
    using (Scope scope = AsyncScopedLifestyle.BeginScope(container))
    {

        scope.WhenScopeEnds(() =>
        {
            // Scope is disposed. Inspect the call stack to find out who 
            // is invoking dispose.
            //Debugger.Break();
            Console.WriteLine("wth");
        });

        //line 37--exception has bubbled up to this point
        return base.OnReceived(request, connectionId, data);   
        
    }
    //return Invoke(() => base.OnReceived(request, connectionId, data));
}
  1. I press continue, and then my breakpoint is hit at Scope.cs line 270. I inspect the Scope object, and it's status is Alive just prior to being set to Disposing on line 270. Here's what the surrounding code looks like from Scope.cs:
protected virtual void Dispose(bool disposing)
{
    if (disposing)
    {
        // We completely block the Dispose method from running in parallel, because there's
        // all kinds of state that needs to be read/written, such as this.state,
        // this.disposables, and this.scopeEndActions. Making this thread-safe with smaller
        // granular locks will be much harder and simply not necessarily, since Dispose
        // should normally only be called from one thread.
        lock (this.syncRoot)
        {
            if (this.state != DisposeState.Alive)
            {
                // Either this instance is already disposed, or a different thread is 
                // currently disposing it. We can break out immediately.
                return;
            }

            //line 270--right here this.state = Alive just prior to executing
            this.state = DisposeState.Disposing;

            try
            {
                this.DisposeRecursively();
            }
            finally
            {
                this.state = DisposeState.Disposed;

                // Remove all references, so we won't hold on to created instances even if 
                // the scope accidentally keeps referenced. This prevents leaking memory.
                this.cachedInstances = null;
                this.scopeEndActions = null;
                this.disposables = null;

                this.manager?.RemoveScope(this);
            }
        }
    }
}

And the callstack at this point in time is:

SimpleInjector.dll!SimpleInjector.Scope.Dispose(bool disposing = true) Line 270    C#    Symbols loaded.
SimpleInjector.dll!SimpleInjector.Scope.Dispose() Line 233    C#    Symbols loaded.

Scs.Core.DependencyInjection.SimpleInjector.dll!Scs.Core.DependencyInjection.SimpleInjector.SimpleInjectorHubDispatcher.OnReceived(Microsoft.AspNet.SignalR.IRequest request = {Microsoft.AspNet.SignalR.Owin.ServerRequest}, string connectionId = "ddc419d3-3c8d-407e-8286-319c1a8a3661", string data = "{\"H\":\"casehub\",\"M\":\"Init\",\"A\":[\"9943\"],\"I\":0}") Line 37    C#    Symbols loaded.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.PersistentConnection.ProcessRequestPostGroupRead.AnonymousMethod__5()    Unknown    Non-user code. Skipped loading symbols.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.TaskAsyncHelper.FromMethod(System.Func<System.Threading.Tasks.Task> func)    Unknown    Non-user code. Skipped loading symbols.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.Transports.WebSocketTransport.OnMessage(string message)    Unknown    Non-user code. Skipped loading symbols.
Microsoft.AspNet.SignalR.Core.dll!Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler.ProcessWebSocketRequestAsync(System.Net.WebSockets.WebSocket webSocket = {System.Web.WebSockets.AspNetWebSocket}, System.Threading.CancellationToken disconnectToken = IsCancellationRequested = false, System.Func<object, System.Threading.Tasks.Task<Microsoft.AspNet.SignalR.WebSockets.WebSocketMessage>> messageRetriever = {Method = {System.Reflection.RuntimeMethodInfo}}, object state = {Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler.ReceiveContext})    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.MoveNextRunner.Run()    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.OutputAsyncCausalityEvents.AnonymousMethod__0()    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Runtime.CompilerServices.TaskAwaiter.OutputWaitEtwEvents.AnonymousMethod__0()    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.GetActionLogDelegate.AnonymousMethod__0()    Unknown    Non-user code. Skipped loading symbols.
System.Web.dll!System.Web.Util.SynchronizationHelper.SafeWrapCallback(System.Action action)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.Tasks.Task.Execute()    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.Tasks.Task.ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot = Id = 11330, Status = Running, Method = "Void <QueueAsynchronous>b__0(System.Threading.Tasks.Task)")    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.Tasks.Task.ExecuteEntry(bool bPreventDoubleExecution)    Unknown    Non-user code. Skipped loading symbols.
mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()    Unknown    Non-user code. Skipped loading symbols.
[Native to Managed Transition]        Annotated Frame

Setup and supporting classes

Right now I have default scoped lifestyle set as follows:

container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
    defaultLifestyle: new WebRequestLifestyle(), //mvc & webapi
    fallbackLifestyle: new AsyncScopedLifestyle() // for signalR?
);

All of my signalR hubs are registered using Lifestyle.Scoped.

Here's my hubConfig setup:

var hubActivator = new SimpleInjectorHubActivator(container);

var hubConfig = new HubConfiguration();
container.EnableSignalR(hubConfig);
hubConfig.EnableDetailedErrors = appSettings["SignalRSendDetailedErrorMessages"] == "true";

hubConfig.Resolver.UseRedis(new RedisScaleoutConfiguration(
    appSettings["redisConnectionString"],
    appSettings["redisSignalREventKey"]));
hubConnectionManager = hubConfig.Resolver.Resolve<IConnectionManager>();

app.MapSignalR<SimpleInjectorHubDispatcher>("/signalr", hubConfig);

var hubPipeline = hubConfig.Resolver.Resolve<IHubPipeline>();
hubPipeline.RequireAuthentication();

I set my MVC resolver to SimpleInjectorDependencyResolver, and Web API resolver to SimpleInjectorWebApiDependencyResolver.

Here's my simpleinjector signalr related classes:

using System;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using SimpleInjector;
using SimpleInjector.Lifestyles;

namespace Scs.Core.DependencyInjection.SimpleInjector
{
    //see https://github.com/simpleinjector/SimpleInjector/issues/232#issuecomment-221837128
    public class SimpleInjectorHubDispatcher : HubDispatcher
    {
        private readonly Container container;
        public SimpleInjectorHubDispatcher(
            Container container, HubConfiguration configuration)
            : base(configuration)
        {
            this.container = container;
        }

        protected override Task OnConnected(IRequest request, string connectionId)
        {
            return Invoke(() => base.OnConnected(request, connectionId));
        }

        protected override Task OnReceived(
            IRequest request, string connectionId, string data)
        {
            using (Scope scope = AsyncScopedLifestyle.BeginScope(container))
            {
                scope.WhenScopeEnds(() =>
                {
                    // Scope is disposed. Inspect the call stack to
                    // find out who is invoking dispose.
                    //Debugger.Break();
                    Console.WriteLine("wth");
                });

                return base.OnReceived(request, connectionId, data);
            }
            //return Invoke(() => base.OnReceived(request, connectionId, data));
        }

        protected override Task OnDisconnected(IRequest request, string connectionId,
            bool stopCalled)
        {
            return Invoke(() => base.OnDisconnected(request, connectionId, stopCalled));
        }

        protected override Task OnReconnected(IRequest request, string connectionId)
        {
            return Invoke(() => base.OnReconnected(request, connectionId));
        }

        private async Task Invoke(Func<Task> method)
        {
            using (var scope = AsyncScopedLifestyle.BeginScope(container))
            {
                scope.WhenScopeEnds(() =>
                {
                    // Scope is disposed. Inspect the call stack to
                    // find out who is invoking dispose.
                    //Debugger.Break();
                    Console.WriteLine("wth");
                });

                await method();
            }                
        }
    }
}
using Microsoft.AspNet.SignalR.Hubs;
using SimpleInjector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace Scs.Core.DependencyInjection.SimpleInjector
{
    //see https://github.com/simpleinjector/SimpleInjector/issues/232#issuecomment-221837128
    public class SimpleInjectorHubActivator : IHubActivator
    {
        private readonly Container container;
        public SimpleInjectorHubActivator(Container container)
        {
            this.container = container;
        }

        public IHub Create(HubDescriptor descriptor)
        {
            try
            {
                return (IHub)container.GetInstance(descriptor.HubType);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
                Console.WriteLine(e.StackTrace);
                throw;
            }
        }
    }
}

Other bits I've found

Now with all this being said, if I flip the default/fallback scoped dependencies so it looks like the following, I no longer get the object disposed exception from within Scope.cs and SimpleInjector does create my signalr hub (CaseHub). However my signalr hub (CaseHub) then throws 'System.ObjectDisposedException' in System.Net.Http.dll when trying to make an api call using HttpClient.GetAsync.

container.Options.DefaultScopedLifestyle = Lifestyle.CreateHybrid(
    defaultLifestyle: new AsyncScopedLifestyle(),
    fallbackLifestyle: new WebRequestLifestyle()
);

Any help here would be appreciated. We're looking to migrate from Autofac for performance reasons. Outside of this issue, I've really enjoyed the opinionated approach Simple Injector takes (so thanks!).

I hijacked another github issue previously pertaining to this: #232. Sorry for that--I wasn't expecting it to be a significant undertaking to resolve.

@camhart
Copy link
Author

camhart commented Apr 16, 2018

I got everything working using AsyncScopedLifestyle as default, and fallback as WebRequestLifestyle. I'm still wrapping my head around why I got the ObjectDisposedException on the HttpClient--but I don't think that's Simple Injector related.

When using WebRequestLifestyle as the default and AsyncScopedLifestyle as the fallback, is it possible that OnReceived is trying to use a WebRequestLifestyle that's already been disposed even though the AsyncScopedLifestyle does exist?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants