public void CleanupMessagingResources(string correlationId, ExpectedResponse expectedResponse) { if (expectedResponse != null) { expectedResponse.Dispose(); } }
public ExpectedResponse PrepareForResponse(string correlationId, BasicProperties basicProperties, AmqpModelContainer model, HttpRequestMessage request, TimeSpan requestTimeout, CancellationToken cancellationToken, TaskCompletionSource<HttpResponseMessage> taskSource) { //Set Reply to queue basicProperties.ReplyTo = RPCStrategyHelpers.DIRECT_REPLY_TO_QUEUENAME_ARG; var rpcModel = (RPCModelContainer)model; var arrival = new ExpectedResponse(rpcModel.ReceivedResponseEvent); rpcModel.ExpectResponse(correlationId, arrival); RPCStrategyHelpers.WaitForResponse(request, arrival, requestTimeout, model, true, cancellationToken, taskSource, () => CleanupMessagingResources(correlationId, arrival)); return arrival; }
public ExpectedResponse PrepareForResponse(string correlationId, BasicProperties basicProperties, AmqpModelContainer model, HttpRequestMessage request, TimeSpan requestTimeout, CancellationToken cancellationToken, TaskCompletionSource <HttpResponseMessage> taskSource) { //Set Reply to queue basicProperties.ReplyTo = RPCStrategyHelpers.DIRECT_REPLY_TO_QUEUENAME_ARG; var rpcModel = (RPCModelContainer)model; var arrival = new ExpectedResponse(rpcModel.ReceivedResponseEvent); rpcModel.ExpectResponse(correlationId, arrival); RPCStrategyHelpers.WaitForResponse(request, arrival, requestTimeout, model, true, cancellationToken, taskSource, () => CleanupMessagingResources(correlationId, arrival)); return(arrival); }
public void CleanupMessagingResources(string correlationId, ExpectedResponse expectedResponse) { if (!String.IsNullOrEmpty(correlationId)) { ExpectedResponse unused; expectedResponses.TryRemove(correlationId, out unused); } if (expectedResponse != null) { expectedResponse.Dispose(); } }
public ExpectedResponse PrepareForResponse(string correlationId, BasicProperties basicProperties, AmqpModelContainer model, HttpRequestMessage request, TimeSpan requestTimeout, CancellationToken cancellationToken, TaskCompletionSource<HttpResponseMessage> taskSource) { //Set Reply to queue basicProperties.ReplyTo = callbackQueueName; //Initialize response arrival object and add to expected responses dictionary var arrival = new ExpectedResponse(); expectedResponses[correlationId] = arrival; RPCStrategyHelpers.WaitForResponse(request, arrival, requestTimeout, model, false, cancellationToken, taskSource, () => CleanupMessagingResources(correlationId, arrival)); return arrival; }
public ExpectedResponse PrepareForResponse(string correlationId, BasicProperties basicProperties, AmqpModelContainer model, HttpRequestMessage request, TimeSpan requestTimeout, CancellationToken cancellationToken, TaskCompletionSource <HttpResponseMessage> taskSource) { //Set Reply to queue basicProperties.ReplyTo = callbackQueueName; //Initialize response arrival object and add to expected responses dictionary var arrival = new ExpectedResponse(); expectedResponses[correlationId] = arrival; RPCStrategyHelpers.WaitForResponse(request, arrival, requestTimeout, model, false, cancellationToken, taskSource, () => CleanupMessagingResources(correlationId, arrival)); return(arrival); }
internal static void ReadAndSignalDelivery(ExpectedResponse expected, BasicDeliverEventArgs evt) { try { expected.Response = HttpResponsePacket.Deserialize(evt.Body); } catch (Exception ex) { expected.DeserializationException = ex; } //NOTE: The ManualResetEventSlim.Set() method can be called after the object has been disposed //So no worries about the Timeout disposing the object before the response comes in. expected.ReceivedEvent.Set(); }
internal static void WaitForResponse(HttpRequestMessage request, ExpectedResponse arrival, TimeSpan requestTimeout, AmqpModelContainer model, bool closeModel, CancellationToken cancellationToken, TaskCompletionSource <HttpResponseMessage> taskSource, Action cleanup) { //Spawning a new task to wait on the MRESlim is slower than using ThreadPool.RegisterWaitForSingleObject // //TODO: Test task vs RegisterWaitForSingleObject modes in a super fast network environment with 40, 100, 200 all the way to 1000 threads to see what method has fastest throughput. #if WAIT_FOR_RESPONSE_IN_TASK_MODE //Wait for response arrival event on a new task Task.Factory.StartNew(() => { bool succeeded = arrival.ReceivedEvent.Wait( requestTimeout.TotalMilliseconds > Int32.MaxValue ? TimeSpan.FromMilliseconds(Int32.MaxValue) : requestTimeout /* Covers InfiniteTimeSpan */, cancellationToken); Thread.MemoryBarrier(); //Ensure non-cached versions of arrival are read //It failed either because it timed out or was cancelled //HttpClient treats both scenarios the same way. try { if (closeModel) { model.Close(); } SetResponseResult(request, !succeeded, arrival, taskSource); } catch { //TODO: Log this: // the code in the try block should be safe so this catch block should never be called, // hoewever, this delegate is called on a separate thread and should be protected. } finally { CleanupMessagingResources(correlationId, arrival); } }, cancellationToken); #else //Wait for response arrival event on the ThreadPool: var localVariableInitLock = new object(); lock (localVariableInitLock) { //TODO: Have cancellationToken signal WaitHandle so that threadpool stops waiting. RegisteredWaitHandle callbackHandle = null; callbackHandle = ThreadPool.RegisterWaitForSingleObject(arrival.ReceivedEvent.WaitHandle, (state, timedOut) => { //TODO: Investigate, this memorybarrier might be unnecessary since the thread is released from the threadpool //after deserializationException and responsePacket is set. Thread.MemoryBarrier(); //Ensure non-cached versions of arrival are read try { //TODO: Check Cancelation Token when it's implemented if (closeModel) { model.Close(); } SetResponseResult(request, timedOut, arrival, taskSource); lock (localVariableInitLock) { callbackHandle.Unregister(null); } } catch { //TODO: Log this: // the code in the try block should be safe so this catch block should never be called, // hoewever, this delegate is called on a separate thread and should be protected. } finally { cleanup(); } }, null, requestTimeout == System.Threading.Timeout.InfiniteTimeSpan ? System.Threading.Timeout.Infinite : (long)requestTimeout.TotalMilliseconds, true); } #endif }
private static void SetResponseResult(HttpRequestMessage request, bool timedOut, ExpectedResponse arrival, TaskCompletionSource <HttpResponseMessage> taskSource) { if (timedOut) { //NOTE: This really ought to return an "Operation Timed Out" WebException and not a Cancellation as noted in the following posts // http://social.msdn.microsoft.com/Forums/en-US/d8d87789-0ac9-4294-84a0-91c9fa27e353/bug-in-httpclientgetasync-should-throw-webexception-not-taskcanceledexception?forum=netfxnetcom&prof=required // http://stackoverflow.com/questions/10547895/how-can-i-tell-when-httpclient-has-timed-out // and http://stackoverflow.com/questions/12666922/distinguish-timeout-from-user-cancellation // // However, for compatibility with the HttpClient, it returns a cancellation // taskSource.SetCanceled(); } else { if (arrival.DeserializationException == null) { if (arrival.Response == null) { //TODO: Log this -- Critical issue (or just assert) } } HttpResponseMessage msg; if (arrival.DeserializationException == null && TryGetHttpResponseMessage(arrival.Response, out msg)) { msg.RequestMessage = request; taskSource.SetResult(msg); } else { taskSource.SetException(RestBusClient.GetWrappedException("An error occurred while reading the response.", arrival.DeserializationException)); } } }
internal static void WaitForResponse (HttpRequestMessage request, ExpectedResponse arrival, TimeSpan requestTimeout, AmqpModelContainer model, bool closeModel, CancellationToken cancellationToken, TaskCompletionSource<HttpResponseMessage> taskSource, Action cleanup) { //Spawning a new task to wait on the MRESlim is slower than using ThreadPool.RegisterWaitForSingleObject // //TODO: Test task vs RegisterWaitForSingleObject modes in a super fast network environment with 40, 100, 200 all the way to 1000 threads to see what method has fastest throughput. #if WAIT_FOR_RESPONSE_IN_TASK_MODE //Wait for response arrival event on a new task Task.Factory.StartNew(() => { bool succeeded = arrival.ReceivedEvent.Wait( requestTimeout.TotalMilliseconds > Int32.MaxValue ? TimeSpan.FromMilliseconds(Int32.MaxValue) : requestTimeout /* Covers InfiniteTimeSpan */, cancellationToken); Thread.MemoryBarrier(); //Ensure non-cached versions of arrival are read //It failed either because it timed out or was cancelled //HttpClient treats both scenarios the same way. try { if (closeModel) model.Close(); SetResponseResult(request, !succeeded, arrival, taskSource); } catch { //TODO: Log this: // the code in the try block should be safe so this catch block should never be called, // hoewever, this delegate is called on a separate thread and should be protected. } finally { CleanupMessagingResources(correlationId, arrival); } }, cancellationToken); #else //Wait for response arrival event on the ThreadPool: var localVariableInitLock = new object(); lock (localVariableInitLock) { //TODO: Have cancellationToken signal WaitHandle so that threadpool stops waiting. RegisteredWaitHandle callbackHandle = null; callbackHandle = ThreadPool.RegisterWaitForSingleObject(arrival.ReceivedEvent.WaitHandle, (state, timedOut) => { //TODO: Investigate, this memorybarrier might be unnecessary since the thread is released from the threadpool //after deserializationException and responsePacket is set. Thread.MemoryBarrier(); //Ensure non-cached versions of arrival are read try { //TODO: Check Cancelation Token when it's implemented if (closeModel) model.Close(); SetResponseResult(request, timedOut, arrival, taskSource); lock (localVariableInitLock) { callbackHandle.Unregister(null); } } catch { //TODO: Log this: // the code in the try block should be safe so this catch block should never be called, // hoewever, this delegate is called on a separate thread and should be protected. } finally { cleanup(); } }, null, requestTimeout == System.Threading.Timeout.InfiniteTimeSpan ? System.Threading.Timeout.Infinite : (long)requestTimeout.TotalMilliseconds, true); } #endif }
internal void Reset() { _correlationId = null; _expected = null; _receivedResponse.Reset(); }
internal static void ReadAndSignalDelivery (ExpectedResponse expected, BasicDeliverEventArgs evt) { try { expected.Response = HttpResponsePacket.Deserialize(evt.Body); } catch (Exception ex) { expected.DeserializationException = ex; } //NOTE: The ManualResetEventSlim.Set() method can be called after the object has been disposed //So no worries about the Timeout disposing the object before the response comes in. expected.ReceivedEvent.Set(); }
/// <summary>Send an HTTP request as an asynchronous operation.</summary> /// <returns>Returns <see cref="T:System.Threading.Tasks.Task`1" />.The task object representing the asynchronous operation.</returns> /// <param name="request">The HTTP request message to send.</param> /// <param name="cancellationToken">The cancellation token to cancel operation.</param> /// <exception cref="T:System.ArgumentNullException">The <paramref name="request" /> was null.</exception> public override Task <HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { if (request == null) { throw new ArgumentNullException("request"); } if (request.RequestUri == null && BaseAddress == null) { throw new InvalidOperationException("The request URI must either be set or BaseAddress must be set"); } if (disposed) { throw new ObjectDisposedException(GetType().FullName); } hasKickStarted = true; PrepareMessage(request); //Get Request Options RequestOptions requestOptions = GetRequestOptions(request); var messageProperties = GetMessagingProperties(requestOptions); //Determine if message expects a response TimeSpan requestTimeout = GetRequestTimeout(requestOptions); //TODO: expectingResponse has a slightly different meaning in publisher confirms //where timespan may be longer than zero but MessageExpectsReply is false //in which case the timeout only applies to how long to wait for the publisher confirmation. bool expectingResponse = requestTimeout != TimeSpan.Zero && GetExpectsReply(request); //Declare messaging resources ExpectedResponse arrival = null; AmqpModelContainer model = null; bool modelClosed = false; string correlationId = null; //Get channel pool and decide on RPC strategy var pool = connectionMgr.GetConnectedPool(); IRPCStrategy rpcStrategy = pool.IsDirectReplyToCapable && !Settings.DisableDirectReplies ? directStrategy : callbackStrategy; try { #region Ensure CallbackQueue is started (If Using CallbackQueue Strategy) rpcStrategy.StartStrategy(pool, expectingResponse); #endregion #region Populate BasicProperties //Fill BasicProperties BasicProperties basicProperties = new BasicProperties(); //Set message delivery mode -- Make message persistent if either: // 1. Properties.Persistent is true // 2. messagingConfig.PersistentMessages is true and Properties.Persistent is null // 3. messagingConfig.PersistentMessages is true and Properties.Persistent is true if (messageProperties.Persistent == true || (messagingConfig.PersistentMessages && messageProperties.Persistent != false)) { basicProperties.Persistent = true; } //Set Exchange Headers var exchangeHeaders = messageProperties.Headers ?? messageMapper.GetHeaders(request); if (exchangeHeaders != null) { basicProperties.Headers = exchangeHeaders; } if (expectingResponse) { //Set CorrelationId correlationId = correlationIdGen.GetNextId(); basicProperties.CorrelationId = correlationId; //Set Expiration if messageProperties doesn't override Client.Timeout, RequestOptions and MessageMapper. if (!messageProperties.Expiration.HasValue && requestTimeout != System.Threading.Timeout.InfiniteTimeSpan && (messagingConfig.MessageExpires == null || messagingConfig.MessageExpires(request))) { if (requestTimeout.TotalMilliseconds > Int32.MaxValue) { basicProperties.Expiration = Int32.MaxValue.ToString(); } else { basicProperties.Expiration = ((int)requestTimeout.TotalMilliseconds).ToString(); } } } else if (!messageProperties.Expiration.HasValue && (messagingConfig.MessageExpires == null || messagingConfig.MessageExpires(request))) { //Request has a zero timeout and the message mapper indicates it should expire and messageproperties expiration is not set: //Set the expiration to zero which means RabbitMQ will only transmit if there is a consumer ready to receive it. //If there is no ready consumer, RabbitMQ drops the message. See https://www.rabbitmq.com/ttl.html basicProperties.Expiration = "0"; } //Set expiration if set in message properties if (messageProperties.Expiration.HasValue) { if (messageProperties.Expiration != System.Threading.Timeout.InfiniteTimeSpan) { var expiration = messageProperties.Expiration.Value.Duration(); if (expiration.TotalMilliseconds > Int32.MaxValue) { basicProperties.Expiration = Int32.MaxValue.ToString(); } else { basicProperties.Expiration = ((int)expiration.TotalMilliseconds).ToString(); } } else { //Infinite Timespan indicates that message should never expire basicProperties.ClearExpiration(); } } #endregion #region Get Ready to Send Message model = rpcStrategy.GetModel(pool, false); var serviceName = (requestOptions == null || requestOptions.ServiceName == null) ? (messageMapper.GetServiceName(request) ?? String.Empty).Trim() : requestOptions.ServiceName.Trim(); RedeclareExchangesAndQueues(model, serviceName); //TODO: Check if cancellation token was set before operation even began var taskSource = new TaskCompletionSource <HttpResponseMessage>(); var exchangeKind = ExchangeKind.Direct; //TODO: Get ExchangeKind from CLient.Settings.ExchangeKind //TODO: Pull exchangeName from a concurrent dictionary that has a key of serviceName, exchangeKind //exchangeKind could be an index into arrays that have concurrentDictionaries. var exchangeName = AmqpUtils.GetExchangeName(messagingConfig, serviceName, exchangeKind); #endregion #region Start waiting for response //Start waiting for response if a request timeout is set. if (expectingResponse) { //TODO: Better to just check if cancellationHasbeen requested instead of checking if it's None if (!cancellationToken.Equals(System.Threading.CancellationToken.None)) { //TODO: Have cancellationtokens cancel event trigger callbackHandle //In fact turn this whole thing into an extension } arrival = rpcStrategy.PrepareForResponse(correlationId, basicProperties, model, request, requestTimeout, cancellationToken, taskSource); } #endregion #region Send Message //TODO: Implement routing to a different exchangeKind via substituting exchangeName //Send message model.Channel.BasicPublish(exchangeName, messageProperties.RoutingKey ?? messageMapper.GetRoutingKey(request, exchangeKind) ?? AmqpUtils.GetWorkQueueRoutingKey(), basicProperties, request.ToHttpRequestPacket().Serialize()); //Close channel if (!expectingResponse || rpcStrategy.ReturnModelAfterSending) { CloseAmqpModel(model); modelClosed = true; } #endregion #region Cleanup if not expecting response //Exit with OK result if no request timeout was set. if (!expectingResponse) { //TODO: Investigate adding a publisher confirm for zero timeout messages so we know that RabbitMQ did pick up the message before replying OK. //Might add extra complexity to this class. //Zero timespan means the client isn't interested in a response taskSource.SetResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = _emptyByteArrayContent }); rpcStrategy.CleanupMessagingResources(correlationId, arrival); } #endregion return(taskSource.Task); } catch (Exception ex) { //TODO: Log this if (model != null && !modelClosed) { if (expectingResponse && model.Flags == ChannelFlags.RPC || model.Flags == ChannelFlags.RPCWithPublisherConfirms) { //Model might still be in use in waiting thread and so unsafe to be recycled model.Discard = true; } CloseAmqpModel(model); } rpcStrategy.CleanupMessagingResources(correlationId, arrival); if (ex is HttpRequestException) { throw; } else { throw GetWrappedException("An error occurred while sending the request.", ex); } } }
public void CleanupMessagingResources(string correlationId, ExpectedResponse expectedResponse) { if (expectedResponse != null) expectedResponse.Dispose(); }
private static void SetResponseResult(HttpRequestMessage request, bool timedOut, ExpectedResponse arrival, TaskCompletionSource<HttpResponseMessage> taskSource) { if (timedOut) { //NOTE: This really ought to return an "Operation Timed Out" WebException and not a Cancellation as noted in the following posts // http://social.msdn.microsoft.com/Forums/en-US/d8d87789-0ac9-4294-84a0-91c9fa27e353/bug-in-httpclientgetasync-should-throw-webexception-not-taskcanceledexception?forum=netfxnetcom&prof=required // http://stackoverflow.com/questions/10547895/how-can-i-tell-when-httpclient-has-timed-out // and http://stackoverflow.com/questions/12666922/distinguish-timeout-from-user-cancellation // // However, for compatibility with the HttpClient, it returns a cancellation // taskSource.SetCanceled(); } else { if (arrival.DeserializationException == null) { if (arrival.Response == null) { //TODO: Log this -- Critical issue (or just assert) } } HttpResponseMessage msg; if (arrival.DeserializationException == null && TryGetHttpResponseMessage(arrival.Response, out msg)) { msg.RequestMessage = request; taskSource.SetResult(msg); } else { taskSource.SetException(RestBusClient.GetWrappedException("An error occurred while reading the response.", arrival.DeserializationException)); } } }
internal void ExpectResponse(string correlationId, ExpectedResponse expected) { _correlationId = correlationId; _expected = expected; }