Example #1
0
        protected IRequestContext CreateRequestContext(IMessage requestMessage)
        {
            var          sessionId = requestMessage.SessionId;
            IUserSession userSession;

            if (!IsSessionRequired)
            {
                userSession = null;
            }
            else if (sessionId == null || !UserSessionCache.TryGetSession(sessionId, out userSession))
            {
                Log.Warning("Unable to find user session for ID: {sessionId}", sessionId);
                return(null);
            }
            return(new RequestContext(requestMessage, userSession));
        }
        private void ProcessSubscription(string subscriptionId,
                                         IMessage message,
                                         RequestStreamHandler <TRequest, TUpdate> requestStreamHandler)
        {
            // In order to prevent races/resource-leaks here we must:
            //  * Monitor session diconnections before attempting to create a request context.
            //      - Otherwise, a session could be destroyed and we would never clean up its resources, as we'd
            //        miss the notification.
            //  * Create the subscription to monitor sessions and add update our state all within a single
            //    critical region. In addition to this, we must ensure session destruction callbacks are
            //    fired on a different thread.
            //      - Otherwise, we may attempt to remove the session's resources before they are added.
            var sessionId        = message.SessionId;
            var replyDestination = message.ReplyTo;
            var subscription     = new CompositeDisposable();

            TRequest request;

            if (!TryDeserializeRequest(subscriptionId, message, out request))
            {
                return;
            }

            var clientId = message.Properties.GetString(OperationKeys.ClientId);

            if (clientId == null)
            {
                Log.Warning("No ClientId found. Ignoring.");
                return;
            }

            var sessionDestroyedHandler = new AnonymousUserSessionHandler(_ => { },
                                                                          _ =>
            {
                lock (_subscriptions)
                {
                    // TODO: Make the Java version clean all resources hooked up here.
                    // ReSharper disable once AccessToDisposedClosure
                    subscription.Dispose();
                    _subscriptions.Remove(subscriptionId);
                }
            });

            lock (_subscriptions)
            {
                // TODO: How do we clean up when IsSessionRequired == false?
                if (IsSessionRequired)
                {
                    subscription.Add(UserSessionCache.Subscribe(sessionId, sessionDestroyedHandler));
                }

                var context = CreateRequestContext(message);
                if (context == null)
                {
                    Log.Warning("Failed to create request context. Ignoring.");
                    subscription.Dispose(); // Don't listen for session destruction if it doesn't exist.
                    return;
                }

                // At this point we know the session exists or existed and we know it will be cleared up (after we
                // exit the critical region) by the sessionResourceCleaner if it is destroyed.
                _subscriptions.Add(subscriptionId, subscription);

                try
                {
                    const int notFinished              = 0;
                    const int finished                 = 1;
                    var       subscriptionState        = notFinished;
                    var       notificationSubscription = requestStreamHandler(context,
                                                                              request,
                                                                              new AnonymousStreamHandler <TUpdate>(
                                                                                  // TODO: I remove the session from the lookup AND ALSO dipose subscription here.
                                                                                  //       This is analagous to the AutoDetachObserver<T> in Rx. Should we do the same in the Java version?
                                                                                  //       Review with John. -ZB
                                                                                  update => OnUpdated(subscriptionId, replyDestination, update),
                                                                                  error =>
                    {
                        if (Interlocked.Exchange(ref subscriptionState, finished) ==
                            notFinished)
                        {
                            OnFailed(subscriptionId, replyDestination, error);
                        }
                    },
                                                                                  () =>
                    {
                        if (Interlocked.Exchange(ref subscriptionState, finished) ==
                            notFinished)
                        {
                            OnCompleted(subscriptionId, replyDestination);
                        }
                    }));
                    subscription.Add(Disposable.Create(() =>
                    {
                        var hasAlreadyFinished = Interlocked.Exchange(ref subscriptionState, finished) == finished;
                        if (!hasAlreadyFinished)
                        {
                            notificationSubscription.Dispose();
                        }
                    }));
                }
                catch (Exception e)
                {
                    const string error = "Failed to process request";
                    OnFailed(subscriptionId, replyDestination, new MessagingException(error, e));
                    Log.Error(error, e);
                }
            }

            SendAck(subscriptionId, replyDestination, clientId);
        }