2016-11-17 16:33:27 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Threading.Tasks;
|
2016-11-16 22:11:05 +00:00
|
|
|
|
using RabbitMQ.Client;
|
2016-11-20 13:34:50 +00:00
|
|
|
|
using RabbitMQ.Client.Exceptions;
|
|
|
|
|
using RabbitMQ.Client.Framing;
|
|
|
|
|
using Tapeti.Tasks;
|
2016-11-16 22:11:05 +00:00
|
|
|
|
|
|
|
|
|
namespace Tapeti.Connection
|
|
|
|
|
{
|
|
|
|
|
public class TapetiWorker
|
|
|
|
|
{
|
2016-11-21 19:54:29 +00:00
|
|
|
|
public TapetiConnectionParams ConnectionParams { get; set; }
|
2016-11-20 13:34:50 +00:00
|
|
|
|
public string PublishExchange { get; set; }
|
2016-11-16 22:11:05 +00:00
|
|
|
|
|
|
|
|
|
|
2016-11-20 13:34:50 +00:00
|
|
|
|
private readonly IMessageSerializer messageSerializer;
|
|
|
|
|
private readonly IRoutingKeyStrategy routingKeyStrategy;
|
|
|
|
|
private readonly Lazy<SingleThreadTaskQueue> taskQueue = new Lazy<SingleThreadTaskQueue>();
|
2016-12-05 22:41:17 +00:00
|
|
|
|
private RabbitMQ.Client.IConnection connection;
|
2016-11-16 22:11:05 +00:00
|
|
|
|
private IModel channel;
|
2016-11-20 13:34:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public TapetiWorker(IMessageSerializer messageSerializer, IRoutingKeyStrategy routingKeyStrategy)
|
|
|
|
|
{
|
|
|
|
|
this.messageSerializer = messageSerializer;
|
|
|
|
|
this.routingKeyStrategy = routingKeyStrategy;
|
|
|
|
|
}
|
2016-11-17 16:33:27 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Task Publish(object message)
|
|
|
|
|
{
|
2016-11-20 13:34:50 +00:00
|
|
|
|
return taskQueue.Value.Add(async () =>
|
2016-11-17 16:33:27 +00:00
|
|
|
|
{
|
2016-11-20 13:34:50 +00:00
|
|
|
|
var properties = new BasicProperties();
|
|
|
|
|
var body = messageSerializer.Serialize(message, properties);
|
|
|
|
|
|
|
|
|
|
(await GetChannel())
|
|
|
|
|
.BasicPublish(PublishExchange, routingKeyStrategy.GetRoutingKey(message.GetType()), false,
|
|
|
|
|
properties, body);
|
|
|
|
|
}).Unwrap();
|
2016-11-17 16:33:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-11-20 13:34:50 +00:00
|
|
|
|
public Task Subscribe(string queueName, IQueueRegistration queueRegistration)
|
2016-11-17 16:33:27 +00:00
|
|
|
|
{
|
2016-11-20 13:34:50 +00:00
|
|
|
|
return taskQueue.Value.Add(async () =>
|
|
|
|
|
{
|
|
|
|
|
(await GetChannel())
|
|
|
|
|
.BasicConsume(queueName, false, new TapetiConsumer(this, messageSerializer, queueRegistration));
|
|
|
|
|
}).Unwrap();
|
2016-11-17 16:33:27 +00:00
|
|
|
|
}
|
2016-11-16 22:11:05 +00:00
|
|
|
|
|
|
|
|
|
|
2016-11-20 13:34:50 +00:00
|
|
|
|
public async Task Subscribe(IQueueRegistration registration)
|
2016-11-16 22:11:05 +00:00
|
|
|
|
{
|
2016-11-20 13:34:50 +00:00
|
|
|
|
var queueName = await taskQueue.Value.Add(async () =>
|
|
|
|
|
registration.BindQueue(await GetChannel()))
|
|
|
|
|
.Unwrap();
|
2016-11-16 22:11:05 +00:00
|
|
|
|
|
2016-11-20 13:34:50 +00:00
|
|
|
|
await Subscribe(queueName, registration);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Task Respond(ulong deliveryTag, ConsumeResponse response)
|
|
|
|
|
{
|
|
|
|
|
return taskQueue.Value.Add(async () =>
|
2016-11-16 22:11:05 +00:00
|
|
|
|
{
|
2016-11-20 13:34:50 +00:00
|
|
|
|
switch (response)
|
|
|
|
|
{
|
|
|
|
|
case ConsumeResponse.Ack:
|
|
|
|
|
(await GetChannel()).BasicAck(deliveryTag, false);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ConsumeResponse.Nack:
|
|
|
|
|
(await GetChannel()).BasicNack(deliveryTag, false, false);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case ConsumeResponse.Requeue:
|
|
|
|
|
(await GetChannel()).BasicNack(deliveryTag, false, true);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}).Unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Task Close()
|
|
|
|
|
{
|
|
|
|
|
if (!taskQueue.IsValueCreated)
|
|
|
|
|
return Task.CompletedTask;
|
2016-11-16 22:11:05 +00:00
|
|
|
|
|
2016-11-20 13:34:50 +00:00
|
|
|
|
return taskQueue.Value.Add(() =>
|
|
|
|
|
{
|
|
|
|
|
if (channel != null)
|
|
|
|
|
{
|
|
|
|
|
channel.Dispose();
|
|
|
|
|
channel = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReSharper disable once InvertIf
|
|
|
|
|
if (connection != null)
|
|
|
|
|
{
|
|
|
|
|
connection.Dispose();
|
|
|
|
|
connection = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
taskQueue.Value.Dispose();
|
|
|
|
|
});
|
2016-11-16 22:11:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2016-11-20 13:34:50 +00:00
|
|
|
|
/// <remarks>
|
|
|
|
|
/// Only call this from a task in the taskQueue to ensure IModel is only used
|
|
|
|
|
/// by a single thread, as is recommended in the RabbitMQ .NET Client documentation.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
private async Task<IModel> GetChannel()
|
2016-11-16 22:11:05 +00:00
|
|
|
|
{
|
|
|
|
|
if (channel != null)
|
|
|
|
|
return channel;
|
|
|
|
|
|
|
|
|
|
var connectionFactory = new ConnectionFactory
|
|
|
|
|
{
|
2016-11-21 19:54:29 +00:00
|
|
|
|
HostName = ConnectionParams.HostName,
|
|
|
|
|
Port = ConnectionParams.Port,
|
|
|
|
|
VirtualHost = ConnectionParams.VirtualHost,
|
|
|
|
|
UserName = ConnectionParams.Username,
|
|
|
|
|
Password = ConnectionParams.Password,
|
2016-11-20 13:34:50 +00:00
|
|
|
|
AutomaticRecoveryEnabled = true,
|
|
|
|
|
RequestedHeartbeat = 30
|
2016-11-16 22:11:05 +00:00
|
|
|
|
};
|
|
|
|
|
|
2016-11-20 13:34:50 +00:00
|
|
|
|
while (true)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
connection = connectionFactory.CreateConnection();
|
|
|
|
|
channel = connection.CreateModel();
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
catch (BrokerUnreachableException)
|
|
|
|
|
{
|
|
|
|
|
await Task.Delay(5000);
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-11-16 22:11:05 +00:00
|
|
|
|
|
|
|
|
|
return channel;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|