/// <summary>
        /// Callback for response handler timeout timer
        /// </summary>
        /// <param name="state"></param>
        private void ResponseHandlerTimeoutTimer(object state)
        {
            lock (this.shutdownLock)
            {
                // If the handler is already shutting down, just return. We dont want user to get unecessary exception responses.
                if (this.shuttingDown)
                {
                    return;
                }

                if (!this.ignoreIsLastResponseProperty)
                {
                    // Atomically swap out the last response.
                    BrokerResponse <TMessage> lastResponse = Interlocked.Exchange <BrokerResponse <TMessage> >(ref this.lastBrokerResponse, null);

                    // If there was last response
                    if (lastResponse != null)
                    {
                        // Send it to the callback
                        this.InvokeCallback(lastResponse);
                    }
                }

                // If the response handler times out, give the handler a timeout exception
                this.InvokeCallback(new BrokerResponse <TMessage>(new TimeoutException(), new UniqueId(Guid.Empty)));

                // close this instance
                this.Close();
            }
        }
        /// <summary>
        /// Invokes correct user callback
        /// </summary>
        /// <param name="brokerResponse">BrokerResponse to pass to callback</param>
        /// <param name="state">State to pass to callback</param>
        private void InvokeCallback(BrokerResponse <TMessage> brokerResponse)
        {
            try
            {
                if (this.callback != null)
                {
                    this.callback.Invoke(brokerResponse);
                }
                else if (this.callbackState != null)
                {
                    this.callbackState.Invoke(brokerResponse, this.state);
                }
                else
                {
                    System.Diagnostics.Debug.Assert(false, "No callback specifed");
                }
            }

            catch (Exception e)
            {
                Utility.LogError("Unhandled exception invoking response handler - {0}", e);
            }
        }
        /// <summary>
        /// Receives response messages from broker's response service
        /// </summary>
        /// <param name="message">Response message</param>
        public void SendResponse(Message message)
        {
            try
            {
                int currentResponseCount = 0;

                // Reset the heartbeat since operation just succeeded
                this.session.ResetHeartbeat();

                // Reset timeout timer since a response was received
                if (this.responseHandlerTimeoutTimer != null)
                {
                    this.responseHandlerTimeoutTimer.Change(this.responseHanderTimeout, 0);
                }

                // Bug #15946: handling client side exception thrown from WebResponseHandler(when talking to rest service)
                if (string.Equals(message.Headers.Action, Constant.WebAPI_ClientSideException, StringComparison.Ordinal))
                {
                    Exception e = message.Properties[Constant.WebAPI_ClientSideException] as Exception;
                    this.InvokeCallback(new BrokerResponse <TMessage>(e, message.Headers.RelatesTo));
                    return;
                }

                // TODO: Consider whether user callback should get an EOM message
                if (!Utility.IsEOM(message))
                {
                    // If the handler is closed, timed out, closed or broker heartbeat signaled, dont forward any more requests to the user.
                    // NOTE: If some responses already slipped through its OK because we prefer that over adding a lock here
                    if (this.shuttingDown)
                    {
                        return;
                    }

                    // Create a BrokerResponse object wrapper for the message object. A copy of the message must be created
                    MessageBuffer messageBuffer = null;
                    try
                    {
                        messageBuffer = message.CreateBufferedCopy(Constant.MaxBufferSize);
                    }
                    catch (Exception e)
                    {
                        Utility.LogError("AsyncResponseCallback.SendResponse received exception - {0}", e);
                        this.InvokeCallback(new BrokerResponse <TMessage>(e, message.Headers.RelatesTo));
                        return;
                    }

                    BrokerResponse <TMessage> brokerResponse = this.CreateResponse(BrokerClientBase.GetActionFromResponseMessage(message),
                                                                                   !message.IsFault ? message.Headers.Action : String.Empty,
                                                                                   messageBuffer,
                                                                                   message.Headers.RelatesTo == null ? SoaHelper.GetMessageId(message) : message.Headers.RelatesTo);

                    BrokerResponse <TMessage> lastResponse = null;

                    if (this.ignoreIsLastResponseProperty)
                    {
                        lastResponse = brokerResponse;
                    }
                    else
                    {
                        // Atomically swap out the last response.
                        lastResponse = Interlocked.Exchange <BrokerResponse <TMessage> >(ref this.lastBrokerResponse, brokerResponse);
                    }

                    // If there was a previous last response
                    if (lastResponse != null)
                    {
                        // Send it to the callback
                        this.InvokeCallback(lastResponse);

                        // Increment response count
                        currentResponseCount = Interlocked.Increment(ref this.currentResponseCount);
                    }

                    // If the caller wants all messages at once, do so.
                    // Else we always request ResponseWindowSize * 2 messages. If we get to
                    // the end of a window, request another
                    if (this.responseWindowSize != Constant.GetResponse_All && currentResponseCount != 0 &&
                        (currentResponseCount + 1) % this.responseWindowSize == 0)
                    {
                        // Call async version and block on completion in order to workaround System.Net.Socket bug #750028
                        try
                        {
                            SessionBase.TraceSource.TraceInformation("GetResponse : currentResponseCount {0} : clientId {1}", currentResponseCount, this.clientId);
                            this.responseService.GetResponses(
                                this.action,
                                this.callbackManagerId,
                                GetResponsePosition.Current,
                                this.responseWindowSize,
                                this.clientId);
                        }
                        catch (FaultException <SessionFault> e)
                        {
                            throw Utility.TranslateFaultException(e);
                        }
                    }
                }

                // If this is a client purged fault, return that exception in a BrokerResponse
                else
                {
                    TypedMessageConverter endOfResponsesConverter = TypedMessageConverter.Create(typeof(EndOfResponses), Constant.EndOfMessageAction);
                    EndOfResponses        endOfResponses          = (EndOfResponses)endOfResponsesConverter.FromMessage(message);

                    switch (endOfResponses.Reason)
                    {
                    case EndOfResponsesReason.ClientPurged:
                        this.InvokeCallback(new BrokerResponse <TMessage>(SessionBase.ClientPurgedException, new UniqueId(Guid.Empty)));
                        break;

                    case EndOfResponsesReason.ClientTimeout:
                        this.InvokeCallback(new BrokerResponse <TMessage>(SessionBase.ClientTimeoutException, new UniqueId(Guid.Empty)));
                        break;

                    default:
                        // Save how many responses are expected (minus 1 for the last response)
                        System.Diagnostics.Debug.Assert(endOfResponses.Count > 0, "Response count should also be positive number");
                        this.expectedResponseCount = endOfResponses.Count - 1;
                        break;
                    }
                }

                if (!this.ignoreIsLastResponseProperty)
                {
                    // Check to see if we receive the EOM message and all the responses.
                    // If so pass last response to callback marked as such. Also make
                    // sure this isnt called again with a lock.
                    if (this.expectedResponseCount == this.currentResponseCount)
                    {
                        lock (this.lastResponseLock)
                        {
                            if (this.expectedResponseCount == this.currentResponseCount)
                            {
                                // Mark the response as the last one
                                this.lastBrokerResponse.isLastResponse = true;

                                // Send last response to callback. Note since currentResponseCount
                                // is incremented after responses are sent to the callback, the
                                // callback will not be processing any other responses at this time.
                                this.InvokeCallback(this.lastBrokerResponse);

                                this.expectedResponseCount = -1;
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                // Log and eat unhandled user exceptions from their calback
                Utility.LogError("Unhandled exception processing response - {0}", e);
            }
        }
Exemple #4
0
        /// <summary>
        ///   <para>Advances the enumerator to the next element of the collection.</para>
        /// </summary>
        /// <returns>
        ///   <para>
        ///     <see cref="System.Boolean" /> object that specifies whether the enumerator successfully advanced to the next element.
        /// True indicates that the enumerator successfully advanced to the next element.
        /// False indicates that the enumerator passed the end of the collection.</para>
        /// </returns>
        /// <exception cref="System.InvalidOperationException">
        ///   <para>The collection was modified after the enumerator was created. </para>
        /// </exception>
        /// <remarks>
        ///   <para>After you create the enumerator or call the
        ///
        /// <see cref="Reset" /> method, the enumerator is positioned before the first element of the collection, and the first call to the
        ///
        /// <see cref="MoveNext" /> method moves the enumerator over the first element of the collection.</para>
        ///   <para>If
        ///
        /// <see cref="MoveNext" /> passes the end of the collection, the enumerator is positioned after the last element in the collection, and
        /// <see cref="MoveNext" /> returns
        /// False. When the enumerator is at this position, subsequent calls to
        /// <see cref="MoveNext" /> also returns
        /// False until you call
        /// <see cref="Reset" />.</para>
        ///   <para>An enumerator remains valid as long as the collection remains unchanged. If you make changes to
        /// the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call to
        /// <see cref="MoveNext" /> or
        /// <see cref="Reset" /> results in an
        /// <see cref="System.InvalidOperationException" />.</para>
        /// </remarks>
        /// <seealso cref="Reset" />
        /// <seealso cref="System.Collections.IEnumerator.MoveNext" />
        public bool MoveNext()
        {
            // If the corresponding BrokerClient is closed or disposed, return no more items
            if (this.close)
            {
                if (!this.session.IsBrokerAvailable)
                {
                    throw SessionBase.GetHeartbeatException(this.isBrokerNodeDown);
                }
                else
                {
                    return(false);
                }
            }

            ThrowIfNeed(this.endOfResponsesReason);

            bool flag1, flag2, flag3;

            lock (this.responsesLock)
            {
                flag1 = this.currentEnumWindow == null || this.currentEnumWindow.Count == 0;
                flag2 = this.responsesWindows.Count == 0;
                flag3 = this.totalResponseCount != this.currentResponseCount;
            }

            // If there is no current window for enumeration or the current one is empty
            if (flag1)
            {
                // If there are no other windows ready for enumeration
                if (flag2)
                {
                    // If all responses have not been returned
                    if (flag3)
                    {
                        // Wait for more response windows to be ready for enumeration
                        int ret = WaitHandle.WaitAny(new WaitHandle[] { this.newResponseWindowOrEOM, this.signalBrokerDown }, this.newResponsesTimeout, false);

                        // If the timeout expires,
                        if (ret == WaitHandle.WaitTimeout)
                        {
                            // Check to see if the receive window has messages
                            if (this.currentReceiveWindow.Count == 0)
                            {
                                // If not return that there are no more messages
                                // RICHCI: 6/7/9 : Changed from returning false to throwing timeout exception
                                //  so user can distinguish between timeout and no more responses
                                throw new TimeoutException(String.Format(SR.TimeoutGetResponse, this.newResponsesTimeout.TotalMilliseconds));
                            }
                            else
                            {
                                // If there are response messages, move the partially filled receive window to
                                // the "ready to read" response windows queue
                                lock (this.responsesLock)
                                {
                                    this.responsesWindows.Enqueue(this.currentReceiveWindow);
                                    this.currentReceiveWindow = new Queue <MessageWindowItem>();
                                }

                                // Fall through to pulling a new window for the current enum window
                            }
                        }

                        // If the broker down event is signaled, throw exception
                        if (ret == 1)
                        {
                            var faultException = this.exceptionCauseBrokerDown as FaultException <SessionFault>;

                            if (faultException != null)
                            {
                                throw Utility.TranslateFaultException(faultException);
                            }
                            else
                            {
                                throw SessionBase.GetHeartbeatException(this.isBrokerNodeDown);
                            }
                        }

                        ThrowIfNeed(this.endOfResponsesReason);

                        // If the BrokerClient is disposed or closed, return there are no more responses
                        if (this.close)
                        {
                            return(false);
                        }

                        // If all we were waiting for was EOM, return there are no more responses
                        if (this.responsesWindows.Count == 0)
                        {
                            return(false);
                        }

                        // Else fall through to pulling a new window for the current enum window
                    }
                    else
                    {
                        // If there are no responses pending, return that there are no more responses
                        return(false);
                    }
                }

                int responseWindowCount = 0;

                // Get a new current enum window
                lock (this.responsesLock)
                {
                    this.currentEnumWindow = this.responsesWindows.Dequeue();
                    responseWindowCount    = this.responsesWindows.Count;
                }

                // If this was the last window
                if (responseWindowCount == 0)
                {
                    // Reset the event so subsequent calls to Move wait
                    this.newResponseWindowOrEOM.Reset();

                    // Do not ask for more responses. The enumerator would ask for more
                    // responses only when outstanding responses is 0.
                }
            }

            // Dequeue the current message
            MessageWindowItem messageWindowItem = this.currentEnumWindow.Dequeue();

            // Bug #15946: handling client side exception thrown from WebResponseHandler(when talking to rest service)
            if (messageWindowItem.CarriedException != null)
            {
                throw messageWindowItem.CarriedException;
            }

            // Create a BrokerResponse object from Message
            this.currentResponse = this.CreateResponse(messageWindowItem.ActionFromResponse, messageWindowItem.ReplyAction, messageWindowItem.MessageBuffer, messageWindowItem.RelatesTo);

            // Return true since Current will have a new element
            return(true);
        }