Servicestack RabbitMQ: Infinite loop fills up dead-letter-queue when RabbitMqProducer cannot redeclare temporary queue in RPC-pattern - servicestack

When I declare a temporary reply queue to be exclusive (e.g. anonymous queue (exclusive=true, autodelete=true) in rpc-pattern), the response message cannot be posted to the specified reply queue (e.g. message.replyTo="amq.gen-Jg_tv8QYxtEQhq0tF30vAA") because RabbitMqProducer.PublishMessage() tries to redeclare the queue with different parameters (exclusive=false), which understandably results in an error.
Unfortunately, the erroneous call to channel.RegisterQueue(queueName) in RabbitMqProducer.PublishMessage() seems to nack the request message in the incoming queue so that, when ServiceStack.Messaging.MessageHandler.DefaultInExceptionHandler tries to acknowlege the request message (to remove it from the incoming queue), the message just stays on top of the incoming queue and gets processed all over again. This procedure repeats indefinitely and results in one dlq-message per iteration which slowly fills up the dlq.
I am wondering,
if ServiceStack handles the case, when ServiceStack.RabbitMq.RabbitMqProducer cannot declare the response queue, correctly
if ServiceStack.RabbitMq.RabbitMqProducer muss always declare the response queue before publishing the response
if it wouldn't be best to have some configuration flag to omit all exchange and queue declaration calls (outside of the first initialization). The RabbitMqProducer would just assume every queue/exchange to be properly set up and just publish the message.
(At the moment our client just declares its response queue to be exclusive=false and everything works fine. But I'd really like to use rabbitmq's built-in temporary queues.)
MQ-Client Code, requires simple "SayHello" service:
const string INQ_QUEUE_NAME = "mq:SayHello.inq";
const string EXCHANGE_NAME="mx.servicestack";
var factory = new ConnectionFactory() { HostName = "192.168.179.110" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
// Create temporary queue and setup bindings
// this works (because "mq:tmp:" stops RabbitMqProducer from redeclaring response queue)
string responseQueueName = "mq:tmp:SayHello_" + Guid.NewGuid().ToString() + ".inq";
channel.QueueDeclare(responseQueueName, false, false, true, null);
// this does NOT work (RabbitMqProducer tries to declare queue again => error):
//string responseQueueName = Guid.NewGuid().ToString() + ".inq";
//channel.QueueDeclare(responseQueueName, false, false, true, null);
// this does NOT work either (RabbitMqProducer tries to declare queue again => error)
//var responseQueueName = channel.QueueDeclare().QueueName;
// publish simple SayHello-Request to standard servicestack exchange ("mx.servicestack") with routing key "mq:SayHello.inq":
var props = channel.CreateBasicProperties();
props.ReplyTo = responseQueueName;
channel.BasicPublish(EXCHANGE_NAME, INQ_QUEUE_NAME, props, Encoding.UTF8.GetBytes("{\"ToName\": \"Chris\"}"));
// consume response from response queue
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(responseQueueName, true, consumer);
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
// print result: should be "Hello, Chris!"
Console.WriteLine(Encoding.UTF8.GetString(ea.Body));
}
}
Everything seems to work fine when RabbitMqProducer does not try to declare the queues, like that:
public void PublishMessage(string exchange, string routingKey, IBasicProperties basicProperties, byte[] body)
{
const bool MustDeclareQueue = false; // new config parameter??
try
{
if (MustDeclareQueue && !Queues.Contains(routingKey))
{
Channel.RegisterQueueByName(routingKey);
Queues = new HashSet<string>(Queues) { routingKey };
}
Channel.BasicPublish(exchange, routingKey, basicProperties, body);
}
catch (OperationInterruptedException ex)
{
if (ex.Is404())
{
Channel.RegisterExchangeByName(exchange);
Channel.BasicPublish(exchange, routingKey, basicProperties, body);
}
throw;
}
}

The issue got adressed in servicestack's version v4.0.32 (fixed in this commit).
The RabbitMqProducer no longer tries to redeclare temporary queues and instead assumes that the reply queue already exist (which solves my problem.)
(The underlying cause of the infinite loop (wrong error handling while publishing response message) probably still exists.)
Edit: Example
The following basic mq-client (which does not use ServiceStackmq client and instead depends directly on rabbitmq's .net-library; it uses ServiceStack.Text for serialization though) can perform generic RPCs:
public class MqClient : IDisposable
{
ConnectionFactory factory = new ConnectionFactory()
{
HostName = "192.168.97.201",
UserName = "guest",
Password = "guest",
//VirtualHost = "test",
Port = AmqpTcpEndpoint.UseDefaultPort,
};
private IConnection connection;
private string exchangeName;
public MqClient(string defaultExchange)
{
this.exchangeName = defaultExchange;
this.connection = factory.CreateConnection();
}
public TResponse RpcCall<TResponse>(IReturn<TResponse> reqDto, string exchange = null)
{
using (var channel = connection.CreateModel())
{
string inq_queue_name = string.Format("mq:{0}.inq", reqDto.GetType().Name);
string responseQueueName = channel.QueueDeclare().QueueName;
var props = channel.CreateBasicProperties();
props.ReplyTo = responseQueueName;
var message = ServiceStack.Text.JsonSerializer.SerializeToString(reqDto);
channel.BasicPublish(exchange ?? this.exchangeName, inq_queue_name, props, UTF8Encoding.UTF8.GetBytes(message));
var consumer = new QueueingBasicConsumer(channel);
channel.BasicConsume(responseQueueName, true, consumer);
var ea = (BasicDeliverEventArgs)consumer.Queue.Dequeue();
//channel.BasicAck(ea.DeliveryTag, false);
string response = UTF8Encoding.UTF8.GetString(ea.Body);
string responseType = ea.BasicProperties.Type;
Console.WriteLine(" [x] New Message of Type '{1}' Received:{2}{0}", response, responseType, Environment.NewLine);
return ServiceStack.Text.JsonSerializer.DeserializeFromString<TResponse>(response);
}
}
~MqClient()
{
this.Dispose();
}
public void Dispose()
{
if (connection != null)
{
this.connection.Dispose();
this.connection = null;
}
}
}
Key points:
client declares anonymous queue (=with empty queue name) channel.QueueDeclare()
server generates queue and returns queue name (amq.gen*)
client adds queue name to message properties (props.ReplyTo = responseQueueName;)
ServiceStack automatically sends response to temporary queue
client picks up response and deserializes
It can be used like that:
using (var mqClient = new MqClient("mx.servicestack"))
{
var pingResponse = mqClient.RpcCall<PingResponse>(new Ping { });
}
Important: You've got to use servicestack version 4.0.32+.

Related

Azure Service Bus SendMessageAsync method terminates and crashes whole program

I created a .NET core 6 project. I added Azure.Messaging.ServiceBus as the dependency. I am using below code to send message to service bus topic.
// See https://aka.ms/new-console-template for more information
using Azure.Messaging.ServiceBus;
using System.Dynamic;
using System.Net;
using System.Text;
using System.Text.Json;
Console.WriteLine("Hello, World!");
Sender t = new Sender();
Sender.Send();
class Sender
{
public static async Task Send()
{
string connectionString = "Endpoint=sb://sb-test-one.servicebus.windows.net/;SharedAccessKeyName=manage;SharedAccessKey=8e+6SWp3skB3AeDlwH6ufGEainEs45353435JzDywz5DU=;";
string topicName = "topicone";
string subscriptionName = "subone";
// The Service Bus client types are safe to cache and use as a singleton for the lifetime
try
{
await using var client = new ServiceBusClient(connectionString, new ServiceBusClientOptions
{
TransportType = ServiceBusTransportType.AmqpWebSockets
});
// create the sender
ServiceBusSender sender = client.CreateSender(topicName);
dynamic data = new ExpandoObject();
data.name = "Abc";
data.age = 6;
// create a message that we can send. UTF-8 encoding is used when providing a string.
var messageBody = JsonSerializer.Serialize(data);
ServiceBusMessage message = new ServiceBusMessage(messageBody);
// send the message
await sender.SendMessageAsync(message);
var s = 10;
}
catch (Exception e)
{
var v = 10;
}
//// create a receiver for our subscription that we can use to receive the message
//ServiceBusReceiver receiver = client.CreateReceiver(topicName, subscriptionName);
//// the received message is a different type as it contains some service set properties
//ServiceBusReceivedMessage receivedMessage = await receiver.ReceiveMessageAsync();
//// get the message body as a string
//string body = receivedMessage.Body.ToString();
//Console.WriteLine(body);
Console.WriteLine("Press any key to end the application");
Console.ReadKey();
}
}
Issue: When I call await sender.SendMessageAsync(message); after this line get executed, the program is actually terminating. It not awating. The whole execution stops after this line.
System is not throwing any exception and service bus is not receiving any message.
I just noticed that all other samples I saw had a default SharedAccessPolicy called RootManageSharedAccessKey policy available by default in the azure portal. For me, I had to create this policy. To my policy I have given Manage, Send, ReceiveAccess.
Needed to change Sender.Send(); to Sender.Send().GetAwaiter().GetResult();

Azure Service Bus Queue: How the ordering of the message work?

public static async Task DoMessage()
{
const int numberOfMessages = 10;
queueClient = new QueueClient(ConnectionString, QueueName);
await SendMessageAsync(numberOfMessages);
await queueClient.CloseAsync();
}
private static async Task SendMessageAsync(int numOfMessages)
{
try
{
for (var i = 0; i < numOfMessages; i++)
{
var messageBody = $"Message {i}";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
message.SessionId = i.ToString();
await queueClient.SendAsync(message);
}
}
catch (Exception e)
{
}
}
This is my sample code to send message to the service bus queue with session id.
My question is if I call DoMessage function 2 times: Let's name it as MessageSet1 and MessageSet2, respectively. Will the MessageSet2 be received and processed by the received azure function who dealing with the receiving ends of the message.
I want to handle in order like MessageSet1 then the MessageSet2 and never handle with MessageSet2 unless MessageSet1 finished.
There are a couple of issues with what you're doing.
First, Azure Functions do not currently support sessions. There's an issue for that you can track.
Second, the sessions you're creating are off. A session should be applied on a set of messages using the same SessionId. Meaning your for loop should be assigning the same SessionId to all the messages in the set. Something like this:
private static async Task SendMessageAsync(int numOfMessages, string sessionID)
{
try
{
var tasks = new List<Task>();
for (var i = 0; i < numOfMessages; i++)
{
var messageBody = $"Message {i}";
var message = new Message(Encoding.UTF8.GetBytes(messageBody));
message.SessionId = sessionId;
tasks.Add(queueClient.SendAsync(message));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
}
catch (Exception e)
{
// handle exception
}
}
For ordered messages using Sessions, see documentation here.

From C# after getting IBM MQ message how to identify message data type? Is it Object or String type?

In our company we are using IBM MQ server for message queueing. For the same queue we are putting both String and Object type data message with the help of methods WriteObject and WriteString.
The challenge is occured when it comes to consumption(read message) of queue. Since the Get data can be both Object or String we need to decide which method to use ReadString or ReadObject on the MQMessage instance. I made workaround like firstly using ReadObject method, if an exception occured then try it with ReadString.
I did not like this workaround, is there any way to identify message data type after calling MQQueue instance's Get method?
Here below you can find my workaround:
public object GetMessage(string queueName) {
MQQueueManager queueManager = new MQQueueManager("queueManagerName", "channel", "connection");
MQGetMessageOptions queueGetMessageOptions = new MQGetMessageOptions();
queueGetMessageOptions.Options = MQC.MQGMO_WAIT + MQC.MQGMO_FAIL_IF_QUIESCING + MQC.MQPMO_SYNCPOINT;
try {
queueRead = queueManager.AccessQueue(queueName, MQC.MQOO_INPUT_AS_Q_DEF + MQC.MQOO_FAIL_IF_QUIESCING);
queueMessage = new MQMessage();
queueMessage.Format = MQC.MQFMT_STRING;
queueRead.Get(queueMessage, queueGetMessageOptions);
try {
var readObject = queueMessage.ReadObject();
return readObject;
} catch (SerializationException) { } // if message in queue not a object
queueMessage.DataOffset = 0;
var stringMsg = queueMessage.ReadString(queueMessage.MessageLength);
return stringMsg;
} catch (MQException exp) {
if (exp.ReasonCode != 2033) {
log.ErrorFormat("MQException: ResonCode: {0}, {1}", exp.ReasonCode, exp.Message);
}
}
return "";
}
What a horrible design.
First off, why are you putting 2 different message types into the same queue? Bad, very bad idea. You should be using 2 different queues. What, does someone think queues are scarce or rare? You should be handing out queues like candy.
Secondly, if you really need to go with this design then you should read the MQ Knowledge Center on MQMD structure. It contains a field called 'Message Type'. Most applications use 'Message Type' to contain either 'MQMT_DATAGRAM' or 'MQMT_REQUEST' but you can set your own values starting with 'MQMT_APPL_FIRST'.
So, define a couple of constants:
public const int MY_MSG_OBJECT = MQC.MQMT_APPL_FIRST + 1;
public const int MY_MSG_STRING = MQC.MQMT_APPL_FIRST + 2;
Hence, the sending application putting a string message would do:
MQMessage sendmsg = new MQMessage();
sendmsg.Format = MQC.MQFMT_STRING;
sendmsg.MessageType = MY_MSG_STRING;
sendmsg.WriteString("This is a test message");
queue.Put(sendmsg, pmo);
and the sending application putting an object message would do:
MQMessage sendmsg = new MQMessage();
sendmsg.Format = MQC.MQFMT_NONE;
sendmsg.MessageType = MY_MSG_OBJECT;
sendmsg.WriteObject(someObject);
queue.Put(sendmsg, pmo);
The receiving application would do:
MQMessage rcvmsg = new MQMessage();
queue.Get(rcvmsg, gmo);
// Check the Message Type
if (rcvmsg.MessageType == MY_MSG_STRING)
{
readString = queue.ReadString();
}
else if (rcvmsg.MessageType == MY_MSG_OBJECT)
{
readObject = queue.ReadObject();
}
else
{
System.Console.Out.WriteLine("Error: Unknown message type.");
}

ServiceStack: httpReq.GetRawBody() is empty

I have a global requestfilter from where i want to log all http traffic using log4net - company restriction. Problem is that the InputStream is always lenght = 0. The soap envelope is desezerialized correctly and execution of service is succesfull, but inputstream is unavailable after first serialization. Is this wrong approach, if i want to log all ingoing and outgoing http traffic? What should i do to accomplish this? I do not want to log the deserialized requestDto.
this.RequestFilters.Add((httpReq, httpResp, requestDto) =>
{
LogManager.LogFactory.GetLogger(this.GetType()).Info(httpReq.GetRawBody());
});
Error seems to occur in type ServiceStack.WebHost.Endpoints.Support.SoapHandler in method, where using statement closes stream without buffering it:
protected static Message GetRequestMessage(Stream inputStream, MessageVersion msgVersion)
{
using (var sr = new StreamReader(inputStream))
{
var requestXml = sr.ReadToEnd();
var doc = new XmlDocument();
doc.LoadXml(requestXml);
var msg = Message.CreateMessage(new XmlNodeReader(doc), int.MaxValue,
msgVersion);
return msg;
}
}
When i try to access GetRawBody on type ServiceStack.WebHost.Endpoints.Extensions the following logic is executed:
public string GetRawBody()
{
if (bufferedStream != null)
{
return bufferedStream.ToArray().FromUtf8Bytes();
}
using (var reader = new StreamReader(InputStream))
{
return reader.ReadToEnd();
}
}
Here I would expect the inpustream to be buffered, since it Inputstreaem is no longer available (lenght = 0).
Isn't this a bug?
Have you seen Request Logger or IRequiresRequestStream. These might help provide some insight on logging requests. Also, I don't believe InputStream would have a length on GET requests.

How can I use WebHttpRelayBinding with application/json requests?

When I try to use a generic message handler, I run into errors when the accept, or content-type is html/xml/json if I use my own type such as text/x-json everything works as expected the message is dispatched to my handlers and the stream returns the data to the webclient. I have stepped through this with a debugger and my code successfully creates the message but something in the servicebus binding chokes and causes the server not to respond. Is there a setting I need to change to allow application/json and make the service bus send raw data rather then trying to reserialize it?
[WebGet( UriTemplate = "*" )]
[OperationContract( AsyncPattern = true )]
public IAsyncResult BeginGet( AsyncCallback callback, object state )
{
var context = WebOperationContext.Current;
return DispatchToHttpServer( context.IncomingRequest, null, context.OutgoingResponse, _config.BufferRequestContent, callback, state );
}
public Message EndGet( IAsyncResult ar )
{
var t = ar as Task<Stream>;
var stream = t.Result;
return StreamMessageHelper.CreateMessage( MessageVersion.None, "GETRESPONSE", stream ?? new MemoryStream() );
}
Instead of using: StreamMessageHelper.CreateMessage, you can use the following one after you change :
WebOperationContext.Current.OutgoingResponse.ContentTYpe = "application/json"
public Message CreateJsonMessage(MessageVersion version, string action, Stream jsonStream)
{
var bodyWriter = new JsonStreamBodyWriter(jsonStream);
var message = Message.CreateMessage(version, action, bodyWriter);
message.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Json));
return message;
}
class JsonStreamBodyWriter : BodyWriter
{
Stream jsonStream;
public JsonStreamBodyWriter(Stream jsonStream)
: base(false)
{
this.jsonStream = jsonStream;
}
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
writer.WriteNode(JsonReaderWriterFactory.CreateJsonReader(this.jsonStream, XmlDictionaryReaderQuotas.Max), false);
writer.Flush();
}
}

Resources