private void TryReceiveMessage() { // NOTE this method _should not_ throw an exception! ServiceBrokerMessage message = null; SqlTransaction transaction = null; try { transaction = BeginTransaction(); logger.Debug("RECEIVE FROM {0}", ServiceBrokerQueue); message = ServiceBrokerWrapper.WaitAndReceive(transaction, ServiceBrokerQueue, waitTimeout); } catch (Exception e) { logger.Error(e, "Exception caught trying to receive from queue {0}. Rolling back and backing off before reconnecting.", ServiceBrokerQueue); TryRollbackTransaction(transaction); TryDisposeTransactionAndConnection(transaction); workerThreadPool.Release(1); Thread.Sleep(1000); // back-off because we will return and loop immediately return; } // No message? That's okay if (message == null) { try { transaction.Commit(); } finally { TryDisposeTransactionAndConnection(transaction); workerThreadPool.Release(1); } return; } // start a Task.Run(() => { // TryHandleHessage _should not_ throw an exception. It will also commit and cleanup the transactions try { TryHandleMessage(transaction, message); } finally { workerThreadPool.Release(1); // release this semaphore back to the pool regardless of what happened } }); }
private static ServiceBrokerMessage ReceiveInternal(IDbTransaction transaction, string queueName, Guid?conversationHandle, bool wait, int?waitTimeout) { EnsureSqlTransaction(transaction); var cmd = transaction.Connection.CreateCommand() as SqlCommand; var query = new StringBuilder(); if (wait) { query.Append("WAITFOR("); } query.Append("RECEIVE TOP(1) "); query.Append("conversation_handle"); query.Append(", service_name, service_contract_name, message_type_name"); query.Append(", message_body"); query.Append(" FROM "); query.Append(queueName); if (conversationHandle.HasValue && conversationHandle.Value != Guid.Empty) { query.Append(" WHERE conversation_handle = @ch"); var param = cmd.Parameters.Add("@ch", SqlDbType.UniqueIdentifier); param.Value = conversationHandle.Value; } if (wait) { query.Append("), "); if (waitTimeout.HasValue && waitTimeout.Value > 0) { query.Append(" TIMEOUT @to"); var param = cmd.Parameters.Add("@to", SqlDbType.Int); param.Value = waitTimeout.Value; cmd.CommandTimeout = 0; } } cmd.CommandText = query.ToString(); cmd.Transaction = transaction as SqlTransaction; using (var dataReader = cmd.ExecuteReader()) { if (dataReader.Read()) { return(ServiceBrokerMessage.Load(dataReader)); } } return(null); }
private void TryHandleMessageInner(SqlTransaction transaction, ServiceBrokerMessage message) { // Only handle transport messages if (message.MessageTypeName != ServiceBrokerMessageType && !message.IsDialogTimerMessage()) { logger.Debug("Ignoring message of type {0} from queue {1}", message.MessageTypeName, ServiceBrokerQueue); return; // ignore } IncomingTransportMessage transportMessage = null; try { if (message.IsDialogTimerMessage()) { transportMessage = TryLoadDialogTimerTimeoutMessage(transaction, message); } else { transportMessage = TryDeserializeTransportMessage(message); } if (!transportMessage.Headers.ContainsKey(StandardHeaders.OriginatingAddress)) { transportMessage.Headers[StandardHeaders.OriginatingAddress] = ServiceBrokerWrapper.TryGetConversationFarService(transaction, message.ConversationHandle); } Exception lastException = null; if (faultManager.MaxRetriesExceeded(transportMessage, out lastException)) { logger.Debug("MaxRetriesExceeded: Moving message {0} from queue {1} to poison message table.", message.ConversationHandle.ToString(), ServiceBrokerQueue); MoveToPoisonMessage(transaction, message, transportMessage, lastException, "MaxRetriesExceeded", MaxRetries); return; // return without error to commit transaction } logger.Debug("Notifying observers of new TransportMessage for message {0} from queue {1}.", message.ConversationHandle, ServiceBrokerQueue); if (this.MessageAvailable != null) { this.MessageAvailable(this, new MessageAvailable(transportMessage)); } } catch (CannotDeserializeMessageException e) { // If we can't deserialize the transport message or inner message, there is no reason to retry. // note: error is already logged so we don't need to log here logger.Debug("DeserializeError: Moving message {0} from queue {1} to poison message table.", message.ConversationHandle.ToString(), ServiceBrokerQueue); MoveToPoisonMessage(transaction, message, transportMessage, e, "DeserializeError", 0); return; // return without error to commit transaction } }
private IncomingTransportMessage TryDeserializeTransportMessage(ServiceBrokerMessage message) { using (var stream = new MemoryStream(message.Body)) { try { return(FastXmlTransportMessageSerializer.Deserialize(message.ConversationHandle.ToString(), stream)); } catch (Exception e) { logger.Error(e, "Cannot deserialize transport message for message {0} from queue {1}.", message.ConversationHandle, ServiceBrokerQueue); throw new CannotDeserializeMessageException(e); } } }
public static ServiceBrokerMessage Load(SqlDataReader reader) { var message = new ServiceBrokerMessage(); message.ConversationHandle = reader.GetGuid(0); message.ServiceName = reader.GetString(1); message.ServiceContractName = reader.GetString(2); message.MessageTypeName = reader.GetString(3); if (!reader.IsDBNull(4)) { message.Body = reader.GetSqlBytes(4).Buffer; } else { message.Body = new byte[0]; } return message; }
public static ServiceBrokerMessage Load(SqlDataReader reader) { var message = new ServiceBrokerMessage(); message.ConversationHandle = reader.GetGuid(0); message.ServiceName = reader.GetString(1); message.ServiceContractName = reader.GetString(2); message.MessageTypeName = reader.GetString(3); if (!reader.IsDBNull(4)) { message.Body = reader.GetSqlBytes(4).Buffer; } else { message.Body = new byte[0]; } return(message); }
private void TryHandleMessage(SqlTransaction transaction, ServiceBrokerMessage message) { logger.Debug("Received message {0} from queue {1}", message.ConversationHandle, ServiceBrokerQueue); // NOTE this method _should not_ throw an exception! // It is responsible for committing or rolling back the SqlTransaction // that was passed in. // We can keep track of the openTransaction on the current thread in case we need it. openTransaction = transaction; try { // this may throw an exception if we need to rollback TryHandleMessageInner(transaction, message); // End the conversation and commit ServiceBrokerWrapper.EndConversation(transaction, message.ConversationHandle); transaction.Commit(); faultManager.ClearFailuresForMessage(message.ConversationHandle.ToString()); logger.Debug("Committed message {0} from queue {1}", message.ConversationHandle, ServiceBrokerQueue); } catch (Exception e) { TryRollbackTransaction(transaction); faultManager.IncrementFailuresForMessage(message.ConversationHandle.ToString(), e); logger.Error(e, "Exception caught handling message {0} from queue {1}. Rolling back transaction to retry.", message.ConversationHandle, ServiceBrokerQueue); #if DEBUG if (System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); } #endif } finally { openTransaction = null; TryDisposeTransactionAndConnection(transaction); // shoud not throw exception } }
private IncomingTransportMessage TryLoadDialogTimerTimeoutMessage(SqlTransaction transaction, ServiceBrokerMessage message) { var messageBuffer = PopTimeoutMessage(transaction, message.ConversationHandle); if (messageBuffer == null || messageBuffer.Length == 0) { var errorMessage = string.Format("Cannot deserialize transport message. DialogTimer TimeoutMessage is missing for for message {0} from queue {1}.", message.ConversationHandle, ServiceBrokerQueue); logger.Error(errorMessage); throw new CannotDeserializeMessageException(errorMessage); } using (var stream = new MemoryStream(messageBuffer)) { try { return FastXmlTransportMessageSerializer.Deserialize(message.ConversationHandle.ToString(), stream); } catch (Exception e) { logger.Error(e, "Cannot deserialize transport message for message {0} from queue {1}.", message.ConversationHandle, ServiceBrokerQueue); throw new CannotDeserializeMessageException(e); } } }
private IncomingTransportMessage TryDeserializeTransportMessage(ServiceBrokerMessage message) { using (var stream = new MemoryStream(message.Body)) { try { return FastXmlTransportMessageSerializer.Deserialize(message.ConversationHandle.ToString(), stream); } catch (Exception e) { logger.Error(e, "Cannot deserialize transport message for message {0} from queue {1}.", message.ConversationHandle, ServiceBrokerQueue); throw new CannotDeserializeMessageException(e); } } }
private void MoveToPoisonMessage(SqlTransaction transaction, ServiceBrokerMessage serviceBrokerMessage, IncomingTransportMessage transportMessage, Exception exception, string errorCode, int retries) { var origin_service_name = ""; if (transportMessage != null && transportMessage.Headers.ContainsKey(StandardHeaders.OriginatingAddress)) { origin_service_name = transportMessage.Headers[StandardHeaders.OriginatingAddress]; } else if (transportMessage != null && transportMessage.Headers.ContainsKey(StandardHeaders.ReplyToAddress)) { origin_service_name = transportMessage.Headers[StandardHeaders.ReplyToAddress]; } else { origin_service_name = ServiceBrokerWrapper.TryGetConversationFarService(transaction, serviceBrokerMessage.ConversationHandle); } try { // write to the message to the PosionMessage table using (var command = transaction.Connection.CreateCommand()) { command.CommandText = "spInsertPoisonMessage"; command.CommandType = CommandType.StoredProcedure; command.Parameters.AddWithValue("@conversation_handle", serviceBrokerMessage.ConversationHandle); command.Parameters.AddWithValue("@origin_service_name", origin_service_name); command.Parameters.AddWithValue("@service_name", this.ServiceBrokerService); command.Parameters.AddWithValue("@queue_name", this.ServiceBrokerQueue); command.Parameters.AddWithValue("@message_body", serviceBrokerMessage.Body); command.Parameters.AddWithValue("@retries", retries); command.Parameters.AddWithValue("@errorCode", errorCode); if (exception != null) command.Parameters.AddWithValue("@errorMessage", FormatErrorMessage(exception)); else command.Parameters.AddWithValue("@errorMessage", DBNull.Value); command.Transaction = transaction; command.ExecuteNonQuery(); } } catch (Exception e) { logger.Fatal(e, "Failed to write PoisonMessage for message {0} from queue {1}", serviceBrokerMessage.ConversationHandle, ServiceBrokerQueue); // suppress -- don't let this exception take down the process } if (PoisonMessageDetected != null) { PoisonMessageDetected(this, new PoisonMessageDetectedEventArgs() { MessageId = serviceBrokerMessage.ConversationHandle.ToString(), OriginServiceName = origin_service_name, ServiceName = this.ServiceBrokerService, QueueName = this.ServiceBrokerQueue, MessageBody = serviceBrokerMessage.Body, Retries = retries, ErrorCode = errorCode, Exception = exception, }); } }
private void MoveToPoisonMessage(SqlTransaction transaction, ServiceBrokerMessage serviceBrokerMessage, IncomingTransportMessage transportMessage, Exception exception, string errorCode, int retries) { var origin_service_name = ""; if (transportMessage != null && transportMessage.Headers.ContainsKey(StandardHeaders.OriginatingAddress)) { origin_service_name = transportMessage.Headers[StandardHeaders.OriginatingAddress]; } else if (transportMessage != null && transportMessage.Headers.ContainsKey(StandardHeaders.ReplyToAddress)) { origin_service_name = transportMessage.Headers[StandardHeaders.ReplyToAddress]; } else { origin_service_name = ServiceBrokerWrapper.TryGetConversationFarService(transaction, serviceBrokerMessage.ConversationHandle); } try { // write to the message to the PosionMessage table using (var command = transaction.Connection.CreateCommand()) { command.CommandText = "spInsertPoisonMessage"; command.CommandType = CommandType.StoredProcedure; command.Parameters.AddWithValue("@conversation_handle", serviceBrokerMessage.ConversationHandle); command.Parameters.AddWithValue("@origin_service_name", origin_service_name); command.Parameters.AddWithValue("@service_name", this.ServiceBrokerService); command.Parameters.AddWithValue("@queue_name", this.ServiceBrokerQueue); command.Parameters.AddWithValue("@message_body", serviceBrokerMessage.Body); command.Parameters.AddWithValue("@retries", retries); command.Parameters.AddWithValue("@errorCode", errorCode); if (exception != null) { command.Parameters.AddWithValue("@errorMessage", FormatErrorMessage(exception)); } else { command.Parameters.AddWithValue("@errorMessage", DBNull.Value); } command.Transaction = transaction; command.ExecuteNonQuery(); } } catch (Exception e) { logger.Fatal(e, "Failed to write PoisonMessage for message {0} from queue {1}", serviceBrokerMessage.ConversationHandle, ServiceBrokerQueue); // suppress -- don't let this exception take down the process } if (PoisonMessageDetected != null) { PoisonMessageDetected(this, new PoisonMessageDetectedEventArgs() { MessageId = serviceBrokerMessage.ConversationHandle.ToString(), OriginServiceName = origin_service_name, ServiceName = this.ServiceBrokerService, QueueName = this.ServiceBrokerQueue, MessageBody = serviceBrokerMessage.Body, Retries = retries, ErrorCode = errorCode, Exception = exception, }); } }
private IncomingTransportMessage TryLoadDialogTimerTimeoutMessage(SqlTransaction transaction, ServiceBrokerMessage message) { var messageBuffer = PopTimeoutMessage(transaction, message.ConversationHandle); if (messageBuffer == null || messageBuffer.Length == 0) { var errorMessage = string.Format("Cannot deserialize transport message. DialogTimer TimeoutMessage is missing for for message {0} from queue {1}.", message.ConversationHandle, ServiceBrokerQueue); logger.Error(errorMessage); throw new CannotDeserializeMessageException(errorMessage); } using (var stream = new MemoryStream(messageBuffer)) { try { return(FastXmlTransportMessageSerializer.Deserialize(message.ConversationHandle.ToString(), stream)); } catch (Exception e) { logger.Error(e, "Cannot deserialize transport message for message {0} from queue {1}.", message.ConversationHandle, ServiceBrokerQueue); throw new CannotDeserializeMessageException(e); } } }