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.Warn("Unable to find user session for ID: " + 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); }