/// <summary> /// Receives new response messages from broker /// </summary> /// <param name="response">Response message</param> void IResponseServiceCallback.SendResponse(Message response) { MessageBuffer messageBuffer = null; EndOfResponses endOfResponses = null; Exception clientSideException = null; bool isEOM = false; // Reset the heartbeat since operation just succeeded this.session.ResetHeartbeat(); try { // Check whether this is an EOM message isEOM = Utility.IsEOM(response); if (!isEOM) { // If not EOM, create a copy of the message's buffer so it can be deserialized // A copy is needed because WCF will dispose this message when the callback returns // Alternatively we could deserialize the entire message but we want // callback to be quick and we may wind up deserializing messages the user // never looks at (i.e. just checks user data or exits enum early) try { messageBuffer = response.CreateBufferedCopy(Constant.MaxBufferSize); } catch (Exception e) { SessionBase.TraceSource.TraceEvent(TraceEventType.Error, 0, "[Session:{0}][BrokerResponseEnumerator] Failed to create message buffer from the response message: {1}", this.session.Id, e); clientSideException = e; } } else { // If EOM, deserialize EndOfResponses message endOfResponses = (EndOfResponses)this.endOfResponsesConverter.FromMessage(response); } string messageId = String.Empty; try { messageId = SoaHelper.GetMessageId(response).ToString(); } catch { // Swallow possible ObjectDisposedException } SessionBase.TraceSource.TraceEvent(TraceEventType.Verbose, 0, "[Session:{0}][BrokerResponseEnumerator] Response received from broker. IsEOM = {1}, MessageId = {2}", this.session.Id, isEOM, messageId); lock (this.responsesLock) { // If the enumerator is disposing or disposed, return if (this.isDisposeCalled) { return; } // If this is not an EOM message if (!isEOM) { string actionFromResponse = BrokerClientBase.GetActionFromResponseMessage(response); // Save the response in the current window MessageWindowItem messageItem = new MessageWindowItem( messageBuffer, actionFromResponse, !response.IsFault ? response.Headers.Action : String.Empty, response.Headers.RelatesTo == null ? SoaHelper.GetMessageId(response) : response.Headers.RelatesTo); // Bug #15946: handling client side exception thrown from WebResponseHandler(when talking to rest service) // To propagate the client side exception to BrokerResponseEnumerator api, store the exception in MessageWindowItem if (string.Equals(response.Headers.Action, Constant.WebAPI_ClientSideException, StringComparison.Ordinal)) { clientSideException = response.Properties[Constant.WebAPI_ClientSideException] as Exception; } messageItem.CarriedException = clientSideException; this.currentReceiveWindow.Enqueue(messageItem); // Increment the current number of responses this.currentResponseCount++; // Lower number of outstanding requests if (Interlocked.Decrement(ref this.outstandingRequestCount) == 0) { try { this.GetMoreResponses(false, ResponseWindowSize); } catch (Exception e) { SessionBase.TraceSource.TraceEvent(TraceEventType.Error, 0, "[Session:{0}][BrokerResponseEnumerator] Failed to get more responses in SendResponse context: {1}", this.session.Id, e); // Send broker down signal since connection to broker might be bad ((IResponseServiceCallback)this).SendBrokerDownSignal(false); } } // If the current window is full or there are no more responses if (this.currentReceiveWindow.Count == ResponseWindowSize || this.currentResponseCount == this.totalResponseCount) { // Disable the flush timer this.flushResponsesTimer.Change(Timeout.Infinite, Timeout.Infinite); // Immediately flush the receive window this.FlushCurrentReceiveWindow(); } // Otherwise reset the flushtimer to the full interval else { this.flushResponsesTimer.Change(this.flushResponsesTimerInterval, Timeout.Infinite); } } else { // If we already received an EOM, ignore any others. We may get multiple EOM since // more responses are requested until we get EOM. This can lead to overrequesting. if (this.totalResponseCount != -1) { return; } // Save total response count this.totalResponseCount = endOfResponses.Count; this.endOfResponsesReason = endOfResponses.Reason; // If there are no more responses if (this.currentResponseCount == this.totalResponseCount) { // Make the current receive window available to enumerator if it has responses if (this.currentReceiveWindow.Count != 0) { this.responsesWindows.Enqueue(this.currentReceiveWindow); this.currentReceiveWindow = new Queue <MessageWindowItem>(); } // Notify enumerator that a new window is available. Notify even if // its empty since it allows MoveNext to exit and return there are no // more items this.newResponseWindowOrEOM.Set(); } } } } catch (Exception e) { // If we get an exception here log and dispose the enum this.Dispose(); SessionBase.TraceSource.TraceEvent(TraceEventType.Error, 0, "[Session:{0}][BrokerResponseEnumerator] Unhandled exception in response enumerator callback :{1}", this.session.Id, e); } }
/// <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); }