xUnit test with callback (other thread) swallows exceptions - c#

I got an xUnit test which does some checks one a propietary TCP protocol implementation. The test launches a mocked server which is instructed to send the consumer in the unit test some data it should process:
[Fact]
public async void EmptyList()
{
string rawMessage = ...
MockServer server = new MockServer();
server.Start();
ManualResetEvent messageReceived = new ManualResetEvent(false);
CancellationTokenSource tokenSource = new CancellationTokenSource();
try
{
using (var cn = new Connection())
{
cn.MessageReceived += (s, e) =>
{
var m = e.Message;
Assert.Equal<CommandId>(CommandId.Command1, m.CommandId);
var l = m.GetList<int>("Users");
Assert.Equal(0, l.Count);
var emptyDict = m.GetComplexData<string>("EmptyComplex");
Assert.Equal(0, emptyDict.Count);
messageReceived.Set();
};
await cn.ConnectAsync(server.Hostname, server.Port, tokenSource.Token);
var receiveTask = cn.Receive(tokenSource.Token);
server.SendMessageToAll(rawMessage); // mock server send the connected client some sample data
messageReceived.WaitOne(TimeSpan.FromSeconds(5));
Assert.False(receiveTask.IsFaulted); // [[A]]
}
}
finally
{
tokenSource.Cancel();
}
}
When one of the asserts in the message handler MessageReceived fails, it does not give the proper error message. The console shows it caused a ThreadAbortException. In fact before inserting the line marked with A the test even completed as a success ignoring the error from the message handler.
So how can I change this that I get proper error message like Does not match expected...? Is there a way the errors in the other thread (here in receiveTask) are handled a proper unit test failures?
Would
if (receiveTask.Exception != null) throw receiveTask.Exception;
be the proper way?
Also I noted that when I use try-finally inside MessageReceived so that the test must not wait for the full timeout (messageReceived.Set()) the task itself does not get to faulted and does not have an exception. So I am not sure this is a reliable way to do this.

Related

Handle network issues when wi-fi is switched off

I am testing .NET version of gRPC to understand how to handle network failures. I put the server to one external machine and debugging the client. The server ticks with a message onnce a second and the client just shows it on the console. So when I stop my local Wi-Fi connection for seconds, then gRPC engine automatically recovers and I even get remaining values. However, if I disable Wi-Fi for longer time like a minute, then it just gets stuck. I don't even get any exceptions so that I can just handle this case and recover manually. This scenario works fine when I close the server app manually, then an exception will occur on the client. This is what I have on the client:
static async Task Main(string[] args)
{
try
{
await Subscribe();
}
catch (Exception)
{
Console.WriteLine("Fail");
Thread.Sleep(1000);
await Main(args);
}
Console.ReadLine();
}
private static async Task Subscribe()
{
using var channel = GrpcChannel.ForAddress("http://x.x.x.x:5555");
var client = new Greeter.GreeterClient(channel);
var replies = client.GerReplies(new HelloRequest { Message = "Test" });
while (await replies.ResponseStream.MoveNext(CancellationToken.None))
{
Console.WriteLine(replies.ResponseStream.Current.Message);
}
Console.WriteLine("Completed");
}
This works when the server app stopped but it doesn't work if I just disable loca Wi-Fi connection on the client side. How can I handle such a case and similar ones?
I've managed to solve it by KeepAlivePingDelay setting:
var handler = new SocketsHttpHandler
{
KeepAlivePingDelay = TimeSpan.FromSeconds(5),
KeepAlivePingTimeout = TimeSpan.FromSeconds(5),
};
using var channel = GrpcChannel.ForAddress("http://x.x.x.x:5555", new GrpcChannelOptions
{
HttpHandler = handler
});
This configuration force gRPC fail after 10 seconds in case of no connection.

mqttnet client not getting subscribed topics

I'm using mqtt.net (https://github.com/chkr1011/MQTTnet) and have written a small class to handle my mqtt client. The client connects to the broker and publishes it's messages successfully. Now I want the client to also react on some topics I subscribe the client to. But this does not seem to work. I do not get any breakpoint hit. This are the relevant parts of my code:
public async Task StartAsync(CancellationToken cancellationToken)
{
//Building the mqtt config
var options = new MqttClientOptionsBuilder()
.WithTcpServer(MqttConfig.Server, MqttConfig.Port)
.WithClientId("HeaterService")
.WithCredentials(MqttConfig.User, MqttConfig.Password)
.WithTls(tlsParameters =>
{
tlsParameters.AllowUntrustedCertificates = true;
})
.WithCleanSession()
.Build();
//Getting an mqtt Instance
MqttClient = new MqttFactory().CreateMqttClient();
//Wiring up all the events...
MqttClient.UseApplicationMessageReceivedHandler( e => { HandleMessageReceived(e.ApplicationMessage); });
MqttClient.UseConnectedHandler(/*async*/ e =>
{
Console.WriteLine("### CONNECTED WITH BROKER ###");
});
await MqttClient.ConnectAsync(options);
}
The client connects successfully to the server and is possible to publish messages.
This is my messagehandler function:
private void HandleMessageReceived(MqttApplicationMessage applicationMessage)
{
Console.WriteLine("### RECEIVED APPLICATION MESSAGE ###");
Console.WriteLine($"+ Topic = {applicationMessage.Topic}");
Console.WriteLine($"+ Payload = {Encoding.UTF8.GetString(applicationMessage.Payload)}");
Console.WriteLine($"+ QoS = {applicationMessage.QualityOfServiceLevel}");
Console.WriteLine($"+ Retain = {applicationMessage.Retain}");
Console.WriteLine();
}
This is my subscribe code:
public async Task SubscribeTopic(string topic)
{
var subscribeResult = await MqttClient.SubscribeAsync(new TopicFilterBuilder()
.WithTopic(topic)
//.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.Build());
Console.WriteLine("### SUBSCRIBED ###");
Console.WriteLine("### Result: " + subscribeResult.Items.FirstOrDefault()?.ResultCode);
Console.WriteLine("### Result: " + subscribeResult.Items.FirstOrDefault()?.TopicFilter);
}
I call this function of my class with "Home/Heater/control/*";
When I use mqtt-explorer to send a test message to the topic "Home/Heater/control/test"
the functionhandler HandleMessageReceived is never hit.
What I am doing wrong?
It seems like you currently don't call your SubscribeTopic(string topic) method in your StartAsync(CancellationToken cancellationToken) method.
Besides that i personally also ran into a few problems when starting out with MQTTNet.
Like #Linuxx said pay attention to the order in wich you call subscribe and connect.
I also recommend adding a Disconnected_Handler to your client to make sure the connection doesn't terminate without your knowing.
It was important to put the subscribe after the connect for me, else I was having the same problem as you.
objClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(ReceivedMessage);
string[] strTopics = { "test/log", "test/log2" };
MqttClientSubscribeOptions objSubOptions = new MqttClientSubscribeOptions();
List<TopicFilter> objTopics = new List<TopicFilter>();
foreach(string strTopic in strTopics)
{
TopicFilter objAdd = new TopicFilter();
objAdd.Topic = strTopic;
objTopics.Add(objAdd);
}
objSubOptions.TopicFilters = objTopics;
objClient.ConnectAsync(objOptions, CancellationToken.None).Wait();
objClient.SubscribeAsync(objSubOptions); //!!!!subscribe goes here!!!!

.NET Client - Waiting for an MQTT response before proceeding to the next request

I have a MQTT calls inside a loop and in each iteration, it should return a response from the subscriber so that I could use the value being forwarded after I published. But the problem is I don't know how would I do it.
I hope you have an idea there or maybe if I'm just not implementing it right, may you guide me through this. Thanks.
Here's my code:
// MyClientMgr
class MyClientMgr{
public long CurrentOutput { get; set; }
public void GetCurrentOutput(MyObjectParameters parameters, MqttClient client)
{
MyMessageObject msg = new MyMessageObject
{
Action = MyEnum.GetOutput,
Data = JsonConvert.SerializeObject(parameters)
}
mq_GetCurrentOutput(msg, client);
}
private void mq_GetCurrentOutput(MyMessageObject msg, MqttClient client)
{
string msgStr = JsonConvert.SerializeObject(msg);
client.Publish("getOutput", Encoding.UTF8.GetBytes(msgStr),
MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
client.MqttMsgPublishReceived += (sender, e) =>{
MyObjectOutput output = JsonConvert.DeserializeObject<MyObjectOutput>(Encoding.UTF8.GetString(e.Message));
CurrentOutput = output;
};
}
}
// MyServerMgr
class MyServerMgr
{
public void InitSubscriptions()
{
mq_GetOutput();
}
private void mq_GetOutput()
{
MqttClient clientSubscribe = new MqttClient(host);
string clientId = Guid.NewGuid().ToString();
clientSubscribe.Connect(clientId);
clientSubscribe.Subscribe(new string[] { "getOutput" }, new byte[] { MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE });
MqttClient clientPublish = new MqttClient(host);
string clientIdPub = Guid.NewGuid().ToString();
clientPublish.Connect(clientIdPub);
clientSubscribe.MqttMsgPublishReceived += (sender, e) => {
MyMessageObj msg = JsonConvert.DeserializeObject<MyMessageObj>(Encoding.UTF8.GetString(e.Message));
var output = msg.Output;
clientPublish.Publish("getOutput", Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(output)), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, false);
}
}
}
// MyCallerClass
class MyCallerClass
{
var host = "test.mqtt.org";
var myServer = new MyServerMgr(host);
var myClient = new MyClientMgr();
myServer.InitSubscriptions();
MqttClient client = new MqttClient(host);
for(int i = 0; i < 10; i++)
{
long output = 0;
MyObjectParameters parameters = {};
myClient.GetCurrentOutput(parameters, client) // here I call the method from my client manager
// to publish the getting of the output and assigned
// below for use, but the problem is the value doesn't
// being passed to the output variable because it is not
// yet returned by the server.
// Is there a way I could wait the process to
// get the response before assigning the output?
output = myClient.CurrentOutput; // output here will always be null
// because the response is not yet forwarded by the server
}
}
I have a loop in my caller class to call the mqtt publish for getting the output, but I have no idea how to get the output before it was assigned, I want to wait for the response first before going to the next.
I've already tried doing a while loop inside like this:
while(output == 0)
{
output = myClient.CurrentOutput;
}
Yes, I can get the output here, but it will slow down the process that much. And sometimes it will fail.
Please help me. Thanks.
It looks like you are trying to do synchronous communication over an asynchronous protocol (MQTT).
By this I mean you want to send a message and then wait for a response, this is not how MQTT works as there is no concept of a reply to a message at the protocol level.
I'm not that familiar with C# so I'll just give an abstract description of possible solution.
My suggestion would be to use a publishing thread, wait/pulse (Look at the Monitor class) to have this block after each publish and have the message handler call pulse when it has received the response.
If the response doesn't contain a wait to identify the original request you will also need a state machine variable to record which request is in progress.
You may want to look at putting a time out on the wait in case the other end does not respond for some reasons.
You can use AutoResetEvent class that has WaitOne() and Set() methods. Using WaitOne() after publish will wait until the message is published and using Set() under client_MqttMsgPublishReceived event will release the wait when the subscriber received the message he subscribed for.

RabbitMQ IModel.Dispose hangs

For some reason this started happening. Didn't yesterday. My client hangs after channel.BasicPublish returns in channel.Dispose. My connection is not bad, i.e. it's in open state and app is not being shutdown as suggested in https://groups.google.com/forum/?fromgroups=#!topic/rabbitmq-discuss/5nzeEqI5qxw. Both ways behave the same:
using (var channel = _connection.CreateModel()) {
//use channel here
}
and
var channel = _connection.CreateModel()
//use channel here
channel.Dispose();
They have bug 25255 on this issue - link.
For now try using a timeout around this call.
private void DisconnectWithTimeout(IConnection connection, int timeoutMillis)
{
var task = Task.Run(() => connection.Dispose());
if (!task.Wait(timeoutMillis))
{
//timeout
throw new TimeoutException("Timeout on connection.Dispose()");
}
}

Invalid Operation Exception In Calling Async Method from inside a Parallel.ForEach Loop

I have inherited a windows service that processes a large number of e-mails in a queue. Sounds simple, Grab queue, send e-mail, if SmtpClient.SendAsync does not return an error from the call back then flag the e-mail in the DB as being sent.. I am using a Semaphore to waitone on the thread so multiple calls can be made to the Async Send method of the SMTP Client. This is the only way I can get the status and per Microsoft docs it has to finish the operation before another call can be made async. So now for the fun part. I decided to use a Parallel.ForEach to get he queue like so. This method is called in the Windows Service OnStart. Please note I have tried calling this method on a separate Thread and get the same results.
I am thinking that either A, I am missing something obvious, due to my lack of knowledge on threading, or something is flat bugged. Most likely A.
private static void ProcessEmailQueue()
{
List<EmailQueue> emailQueue =
_repository.Select<EmailQueue>().Where(x => x.EmailStatuses.EmailStatus == "Pending").ToList();
Parallel.ForEach(emailQueue, message =>
{
_smtpMail.FromAddress = message.FromAddress;
_smtpMail.ToAddress = message.ToAddress;
_smtpMail.Subject = message.Subject;
_smtpMail.SendAsHtml = message.IsHtml > 0;
_smtpMail.MessageBody = message.MessageBody;
_smtpMail.UserToken = message.EmailQueueID;
bool sendStatus = _smtpMail.SendMessage();
// THIS BLOWS UP with InvalidOperation Exception
});
}
Here is the SMTP Method being called from withing the loop.
public bool SendMessage()
{
mailSendSemaphore = new Semaphore(0, 10); // This is defined as private static Semaphore mailSendSemaphore;
try
{
var fromAddress = new MailAddress(FromAddress);
var toAddress = new MailAddress(ToAddress);
using (var mailMessage = new MailMessage(fromAddress, toAddress))
{
mailMessage.Subject = Subject;
mailMessage.IsBodyHtml = SendAsHtml;
mailMessage.Body = MessageBody;
Envelope = mailMessage;
smtp.SendCompleted += smtp_SendCompleted;
smtp.SendAsync(mailMessage, UserToken);
mailSendSemaphore.WaitOne();
return _mailSent;
}
}
catch (Exception exception)
{
_logger.Error(exception);
return _mailSent;
}
}
CALLBACK For Smtp Send
private void smtp_SendCompleted(object sender, AsyncCompletedEventArgs e)
{
if (e.Cancelled)
{
}
if (e.Error != null)
{
}
else
{
_mailSent = true;
}
mailSendSemaphore.Release(2);
}
Here is the Exception, took a few to get it for some odd reason.
System.InvalidOperationException was unhandled by user code
Message=An asynchronous call is already in progress. It must be completed or canceled before you can call this method.
Source=System
StackTrace:
at System.Net.Mail.SmtpClient.SendAsync(MailMessage message, Object userToken)
at DFW.Infrastructure.Communications.SmtpMail.SendMessage() in SmtpMail.cs:line 71
at EmaiProcessorService.EmailQueueService.b_0(EmailQueue message) in Service1.cs:line 57
at System.Threading.Tasks.Parallel.<>c_DisplayClass2d2.<ForEachWorker>b__23(Int32 i)
at System.Threading.Tasks.Parallel.<>c__DisplayClassf1.b__c()
InnerException:
Seems my waitone is getting obliterated by System.Threading.Tasks.Parallel
Okay, now that we've got the error text, it seems fairly clear:
Message=An asynchronous call is already in progress. It must be completed or canceled before you can call this method.
This concurs with the documentation:
Two simple options:
Create a fixed number of clients, and a queue of messages to send. Make each client take a message from the queue each time it finishes, until the queue is empty. BlockingCollection<T> is good for this.
Create a new SmtpClient per message. This could cause you to effectively launch a DOS attack on your SMTP server, which isn't ideal.
To be honest, it's not really clear why you're using SendAsync when you're then just waiting for the message to be sent anyway...
I'm not clear on why you're using a Semaphore here, but you're almost certainly using it incorrectly. You're creating a new semaphore instance for each call to SendMessage. Also, you're calling WaitOne on it once, and then calling Release(2), so eventually you'll have more releases than acquires. That's probably what causes your InvalidOperationException.
It doesn't do you any good to parallelize processing of the email queue, since you can only send one message at a time. And trying to do it asynchronously inside of the Parallel.Foreach is just more needless complication.
You're better off using something like ThreadPool.QueueUserWorkItem, and having a simple loop that sends one message at a time.
List<EmailQueue> emailQueue =
_repository.Select<EmailQueue>().Where(x => x.EmailStatuses.EmailStatus == "Pending").ToList();
ThreadPool.QueueUserWorkItem(ProcessEmailQueue, emailQueue);
void ProcessEmailQueue(object state)
{
List<EmailQueue> emailQueue = (List<EmailQueue>)state;
foreach (var message in EmailQueue)
{
// Format and send message here.
}
}
Alternatively, you can do the same thing with a Task. The point is that you just need a single thread to process the queue sequentially. Since you can't send more than one message at a time, Parallel.ForEach doesn't do you any good.
EDIT:
If you need to do multiple sends at a time, you can probably modify your original code. First, initialize the semaphore at class scope:
private static Semaphore mailSendSemaphore = new Semaphore(10, 10);
Then, in your SendMessage method:
bool SendMessage()
{
// acquire semaphore. This will block until there's a slot available.
mailSendSemaphore.WaitOne();
try
{
// do all your processing here, including sending the message.
// use Send rather than SendAsync
}
finally
{
mailSendSemaphore.Release();
}
}
There's no need to use SendAsync.

Categories

Resources