locked
Static Class SignalR Client Shared Across Multiple Web Services RRS feed

  • Question

  • User514098894 posted

    I have several asmx web services and REST svc services running on a server along with a couple of SignalR hubs.  I am trying to build a shared DLL to interface to each of my SignalR hubs that the various web services can instantiate and use as a client interface to the hub to pump messages from the web service methods.  Originally, I implemented the connection and hub creation in each web service method, destroying after calling the SignalR server method.  This worked fine, but the overhead of creating the connection and hub, connecting to the Hub and calling in each call the the web service was slow and seemed like the wrong approach. 

    I created a static class in a standalone DLL project that I reference from my web service.  This creates the connection and hub proxy one time and allows any method to call through this interface to talk to the Hub. However, after trying to this, I started getting nullreferenceexceptions on the thread and it was crashing the IIS worker threat causing the application instance to start over.  This continued at a rate of 1 every minute or so.  For the life of me I cannot figure out what is happening.  Here is the exception:

    An unhandled exception occurred and the process was terminated. Application ID: /LM/W3SVC/394760691/ROOT/webservices Process ID: 2776 Exception: System.NullReferenceException Message: Object reference not set to an instance of an object. StackTrace: at System.Web.ThreadContext.AssociateWithCurrentThread(Boolean setImpersonationContext) at System.Web.HttpApplication.OnThreadEnterPrivate(Boolean setImpersonationContext) at System.Web.LegacyAspNetSynchronizationContext.CallCallbackPossiblyUnderLock(SendOrPostCallback callback, Object state) at System.Web.LegacyAspNetSynchronizationContext.CallCallback(SendOrPostCallback callback, Object state) at System.Web.LegacyAspNetSynchronizationContext.Post(SendOrPostCallback callback, Object state) at System.Threading.Tasks.SynchronizationContextAwaitTaskContinuation.PostAction(Object state) at System.Threading.Tasks.AwaitTaskContinuation.RunCallback(ContextCallback callback, Object state, Task& currentTask) --- End of stack trace from previous location where exception was thrown --- at System.Threading.Tasks.AwaitTaskContinuation.<>c.b__18_0(Object s) at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() at System.Threading.ThreadPoolWorkQueue.Dispatch() at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

    Every call to a method in this interface checks the connection state and if disconnected calls a method to connect.  This kept the connection from starting when the application started because it was causing some race conditions between a couple of services that were connecting to both hubs.  Here is the code:

            // Public method to set the HubName for the instance
            public static string HubName
            {
                get
                {
                    return _HubName;
                }
                set
                {
                    _HubName = value;
                }
            }
    
            // Public method to set the HubConnection string for the instance
            public static string HubConnection
            {
                get
                {
                    return _HubConnection;
                }
                set
                {
                    _HubConnection = value;
                }
            }
    
            // Public method to set the Application Id for the instance
            public static string ApplicationId
            {
                get
                {
                    return _ApplicationID;
                }
                set
                {
                    _ApplicationID = value;
                }
            }
    
            private static bool CreateConnection()
            {
                bool result = false;
    
                try
                {
                    conn = new HubConnection(_HubConnection, String.Format("token={0}&applicationId={1}", _ApplicationID, _ApplicationID));
                    conn.ConnectionSlow += conn_ConnectionSlow;
                    conn.Reconnecting += conn_Reconnecting;
                    conn.Reconnected += conn_Reconnected;
                    conn.Error += conn_Error;
                    conn.TraceLevel = TraceLevels.All;
                    conn.TraceWriter = Console.Out;
                    hub = conn.CreateHubProxy(_HubName != null ? _HubName : "Iris");
                }
                catch (Exception ex)
                {
                    log.Error(String.Format("IrisInterface: CreateConnection - ApplicationId {0}", _ApplicationID), ex);
                }
    
                return result;
            }
       
            public static bool ConnectToHub()
            {
                bool result = false;
                bool connExists = false;
    
                try
                {
                    if (conn == null)
                        connExists = CreateConnection();
                    else
                        connExists = true;
                    if (connExists)
                    {
                        if (conn.State == ConnectionState.Connected)
                            result = true;
                        else
                        {
                            DateTime RetryTill = DateTime.UtcNow.AddSeconds(_RetryDuration);
                            while (DateTime.UtcNow < RetryTill && !result)
                            {
                                conn.Closed -= conn_Closed;
                                conn.Start().Wait();
                                if (conn.State == ConnectionState.Connected)
                                {
                                    conn.Closed += conn_Closed;
                                    result = true;
                                }
                            }
                            if (!result)
                                log.Error(String.Format("IrisInterface: ConnectToHub ApplicationId {0}  Unable to connect to hub", _ApplicationID));
                        }
                    }
                }
                catch (Exception ex)
                {
                    log.Error(String.Format("IrisInterface: ConnectToHub - ApplicationId {0}", _ApplicationID), ex);
                }
    
                return result;
            }
            public static void SendObjectChanged(string domainType, long domainId, string subDomainType, long subDomainId)
            {
                if (ConnectToHub())
                    hub.Invoke("SendObjectChanged", domainType, domainId, subDomainType, subDomainId);
            }
    
    

    No exception is caught in my application logging.  Looking at the logs for IIS I found the exception causing the IIS worker process to crash.  Any ideas or observations would be welcome.

    Thank you,

    Steve

    Friday, June 22, 2018 2:21 AM

All replies

  • User61956409 posted

    Hi Steve,

    after trying to this, I started getting nullreferenceexceptions on the thread and it was crashing the IIS worker threat causing the application instance to start over. 

    Do you mean that it can not communiticate with hub server anymore after you applied this approach? Besides, this section show a approach with example about how to use the signalr context from the singleton class instance, you can refer to it.

    With Regards,

    Fei Han

    Monday, June 25, 2018 8:46 AM
  • User514098894 posted

    I have looked at that but I believe this approach only works when only one hub is running on the same server.  This shared class is used by several applications and the hubs exist on different servers.

    We did find an issue with the code above and have fixed it.  It resolved the majority of the issues but we came across something new that we are trying to figure out. Occasionally, the conn conn_error is thrown and the following error is logged.  In that method I just restart the conn.  Since this is happening frequently I would like to understand what is causing it and see if something can be done to prevent it (config, etc.):

    System.Net.WebSockets.WebSocketException (0x80004005): An internal WebSocket error occurred. Please see the innerException, if present, for more details.  ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
       at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
       at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
       --- End of inner exception stack trace ---
       at System.Net.Security._SslStream.EndRead(IAsyncResult asyncResult)
       at System.Net.TlsStream.EndRead(IAsyncResult asyncResult)
       at System.Net.PooledStream.EndRead(IAsyncResult asyncResult)
       at System.IO.Stream.<>c.<BeginEndReadAsync>b__43_1(Stream stream, IAsyncResult asyncResult)
       at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Net.WebSockets.WebSocketConnectionStream.<ReadAsync>d__21.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
       at System.Net.WebSockets.WebSocketBase.WebSocketOperation.<Process>d__19.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Net.WebSockets.WebSocketBase.<ReceiveAsyncCore>d__45.MoveNext()
       at System.Net.WebSockets.WebSocketBase.ThrowIfConvertibleException(String methodName, Exception exception, CancellationToken cancellationToken, Boolean aborted)
       at System.Net.WebSockets.WebSocketBase.<ReceiveAsyncCore>d__45.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at Microsoft.AspNet.SignalR.WebSockets.WebSocketMessageReader.<ReadMessageAsync>d__3.MoveNext()
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at Microsoft.AspNet.SignalR.WebSockets.WebSocketHandler.<ProcessWebSocketRequestAsync>d__25.MoveNext()

    Steve

    Monday, June 25, 2018 6:41 PM