/// <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); } }
/// <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); }