@@ -1,6 +1,6 @@ | |||||
<Project> | <Project> | ||||
<Import Project="build\version.props" /> | |||||
<Import Project="..\build\version.props" /> | |||||
<PropertyGroup Label="Package"> | <PropertyGroup Label="Package"> | ||||
<Product>CAP</Product> | <Product>CAP</Product> |
@@ -71,7 +71,6 @@ namespace DotNetCore.CAP.AzureServiceBus | |||||
public void Listening(TimeSpan timeout, CancellationToken cancellationToken) | public void Listening(TimeSpan timeout, CancellationToken cancellationToken) | ||||
{ | { | ||||
_consumerClient.RegisterMessageHandler(OnConsumerReceived, | _consumerClient.RegisterMessageHandler(OnConsumerReceived, | ||||
new MessageHandlerOptions(OnExceptionReceived) | new MessageHandlerOptions(OnExceptionReceived) | ||||
{ | { | ||||
@@ -20,8 +20,15 @@ namespace DotNetCore.CAP.AzureServiceBus | |||||
public IConsumerClient Create(string groupId) | public IConsumerClient Create(string groupId) | ||||
{ | { | ||||
var logger = _loggerFactory.CreateLogger(typeof(AzureServiceBusConsumerClient)); | |||||
return new AzureServiceBusConsumerClient(logger, groupId, _asbOptions); | |||||
try | |||||
{ | |||||
var logger = _loggerFactory.CreateLogger(typeof(AzureServiceBusConsumerClient)); | |||||
return new AzureServiceBusConsumerClient(logger, groupId, _asbOptions); | |||||
} | |||||
catch (System.Exception e) | |||||
{ | |||||
throw new BrokerConnectionException(e); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -21,6 +21,8 @@ namespace DotNetCore.CAP.Kafka | |||||
_groupId = groupId; | _groupId = groupId; | ||||
_kafkaOptions = options ?? throw new ArgumentNullException(nameof(options)); | _kafkaOptions = options ?? throw new ArgumentNullException(nameof(options)); | ||||
StringDeserializer = new StringDeserializer(Encoding.UTF8); | StringDeserializer = new StringDeserializer(Encoding.UTF8); | ||||
InitKafkaClient(); | |||||
} | } | ||||
public IDeserializer<string> StringDeserializer { get; set; } | public IDeserializer<string> StringDeserializer { get; set; } | ||||
@@ -38,11 +40,6 @@ namespace DotNetCore.CAP.Kafka | |||||
throw new ArgumentNullException(nameof(topics)); | throw new ArgumentNullException(nameof(topics)); | ||||
} | } | ||||
if (_consumerClient == null) | |||||
{ | |||||
InitKafkaClient(); | |||||
} | |||||
_consumerClient.Subscribe(topics); | _consumerClient.Subscribe(topics); | ||||
} | } | ||||
@@ -14,7 +14,14 @@ namespace DotNetCore.CAP.Kafka | |||||
public IConsumerClient Create(string groupId) | public IConsumerClient Create(string groupId) | ||||
{ | { | ||||
return new KafkaConsumerClient(groupId, _kafkaOptions); | |||||
try | |||||
{ | |||||
return new KafkaConsumerClient(groupId, _kafkaOptions); | |||||
} | |||||
catch (System.Exception e) | |||||
{ | |||||
throw new BrokerConnectionException(e); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -8,7 +8,6 @@ namespace DotNetCore.CAP.RabbitMQ | |||||
private readonly IConnectionChannelPool _connectionChannelPool; | private readonly IConnectionChannelPool _connectionChannelPool; | ||||
private readonly RabbitMQOptions _rabbitMQOptions; | private readonly RabbitMQOptions _rabbitMQOptions; | ||||
public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, IConnectionChannelPool channelPool) | public RabbitMQConsumerClientFactory(RabbitMQOptions rabbitMQOptions, IConnectionChannelPool channelPool) | ||||
{ | { | ||||
_rabbitMQOptions = rabbitMQOptions; | _rabbitMQOptions = rabbitMQOptions; | ||||
@@ -17,7 +16,14 @@ namespace DotNetCore.CAP.RabbitMQ | |||||
public IConsumerClient Create(string groupId) | public IConsumerClient Create(string groupId) | ||||
{ | { | ||||
return new RabbitMQConsumerClient(groupId, _connectionChannelPool, _rabbitMQOptions); | |||||
try | |||||
{ | |||||
return new RabbitMQConsumerClient(groupId, _connectionChannelPool, _rabbitMQOptions); | |||||
} | |||||
catch (System.Exception e) | |||||
{ | |||||
throw new BrokerConnectionException(e); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -0,0 +1,13 @@ | |||||
using System; | |||||
namespace DotNetCore.CAP | |||||
{ | |||||
public class BrokerConnectionException : Exception | |||||
{ | |||||
public BrokerConnectionException(Exception innerException) | |||||
: base("Broker Unreachable", innerException) | |||||
{ | |||||
} | |||||
} | |||||
} |
@@ -50,12 +50,15 @@ namespace Microsoft.Extensions.DependencyInjection | |||||
services.TryAddSingleton<MethodMatcherCache>(); | services.TryAddSingleton<MethodMatcherCache>(); | ||||
//Processors | //Processors | ||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IProcessingServer, ConsumerHandler>()); | |||||
services.TryAddSingleton<IConsumerRegister, ConsumerRegister>(); | |||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IProcessingServer, CapProcessingServer>()); | services.TryAddEnumerable(ServiceDescriptor.Singleton<IProcessingServer, CapProcessingServer>()); | ||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IProcessingServer, ConsumerRegister>()); | |||||
services.TryAddSingleton<IStateChanger, StateChanger>(); | services.TryAddSingleton<IStateChanger, StateChanger>(); | ||||
//Queue's message processor | //Queue's message processor | ||||
services.TryAddSingleton<NeedRetryMessageProcessor>(); | services.TryAddSingleton<NeedRetryMessageProcessor>(); | ||||
services.TryAddSingleton<TransportCheckProcessor>(); | |||||
//Sender and Executors | //Sender and Executors | ||||
services.TryAddSingleton<IDispatcher, Dispatcher>(); | services.TryAddSingleton<IDispatcher, Dispatcher>(); | ||||
@@ -14,29 +14,30 @@ using Microsoft.Extensions.Logging; | |||||
namespace DotNetCore.CAP | namespace DotNetCore.CAP | ||||
{ | { | ||||
internal class ConsumerHandler : IConsumerHandler | |||||
internal class ConsumerRegister : IConsumerRegister | |||||
{ | { | ||||
private readonly IStorageConnection _connection; | private readonly IStorageConnection _connection; | ||||
private readonly IConsumerClientFactory _consumerClientFactory; | private readonly IConsumerClientFactory _consumerClientFactory; | ||||
private readonly CancellationTokenSource _cts; | |||||
private readonly IDispatcher _dispatcher; | private readonly IDispatcher _dispatcher; | ||||
private readonly ILogger _logger; | private readonly ILogger _logger; | ||||
private readonly TimeSpan _pollingDelay = TimeSpan.FromSeconds(1); | private readonly TimeSpan _pollingDelay = TimeSpan.FromSeconds(1); | ||||
private readonly MethodMatcherCache _selector; | private readonly MethodMatcherCache _selector; | ||||
private CancellationTokenSource _cts; | |||||
private string _serverAddress; | private string _serverAddress; | ||||
private Task _compositeTask; | private Task _compositeTask; | ||||
private bool _disposed; | private bool _disposed; | ||||
private static bool _isHealthy = true; | |||||
// diagnostics listener | // diagnostics listener | ||||
// ReSharper disable once InconsistentNaming | // ReSharper disable once InconsistentNaming | ||||
private static readonly DiagnosticListener s_diagnosticListener = | private static readonly DiagnosticListener s_diagnosticListener = | ||||
new DiagnosticListener(CapDiagnosticListenerExtensions.DiagnosticListenerName); | new DiagnosticListener(CapDiagnosticListenerExtensions.DiagnosticListenerName); | ||||
public ConsumerHandler(IConsumerClientFactory consumerClientFactory, | |||||
public ConsumerRegister(IConsumerClientFactory consumerClientFactory, | |||||
IDispatcher dispatcher, | IDispatcher dispatcher, | ||||
IStorageConnection connection, | IStorageConnection connection, | ||||
ILogger<ConsumerHandler> logger, | |||||
ILogger<ConsumerRegister> logger, | |||||
MethodMatcherCache selector) | MethodMatcherCache selector) | ||||
{ | { | ||||
_selector = selector; | _selector = selector; | ||||
@@ -44,26 +45,44 @@ namespace DotNetCore.CAP | |||||
_consumerClientFactory = consumerClientFactory; | _consumerClientFactory = consumerClientFactory; | ||||
_dispatcher = dispatcher; | _dispatcher = dispatcher; | ||||
_connection = connection; | _connection = connection; | ||||
_cts = new CancellationTokenSource(); | |||||
} | |||||
public bool IsHealthy() | |||||
{ | |||||
return _isHealthy; | |||||
} | } | ||||
public void Start() | public void Start() | ||||
{ | { | ||||
_cts = new CancellationTokenSource(); | |||||
var groupingMatches = _selector.GetCandidatesMethodsOfGroupNameGrouped(); | var groupingMatches = _selector.GetCandidatesMethodsOfGroupNameGrouped(); | ||||
foreach (var matchGroup in groupingMatches) | foreach (var matchGroup in groupingMatches) | ||||
{ | { | ||||
Task.Factory.StartNew(() => | Task.Factory.StartNew(() => | ||||
{ | { | ||||
using (var client = _consumerClientFactory.Create(matchGroup.Key)) | |||||
try | |||||
{ | { | ||||
_serverAddress = client.ServersAddress; | |||||
using (var client = _consumerClientFactory.Create(matchGroup.Key)) | |||||
{ | |||||
_serverAddress = client.ServersAddress; | |||||
RegisterMessageProcessor(client); | |||||
RegisterMessageProcessor(client); | |||||
client.Subscribe(matchGroup.Value.Select(x => x.Attribute.Name)); | |||||
client.Subscribe(matchGroup.Value.Select(x => x.Attribute.Name)); | |||||
client.Listening(_pollingDelay, _cts.Token); | |||||
client.Listening(_pollingDelay, _cts.Token); | |||||
} | |||||
} | |||||
catch (OperationCanceledException) | |||||
{ | |||||
//ignore | |||||
} | |||||
catch (BrokerConnectionException e) | |||||
{ | |||||
_isHealthy = false; | |||||
_logger.LogError(e, e.Message); | |||||
} | } | ||||
}, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); | }, _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); | ||||
} | } | ||||
@@ -71,6 +90,18 @@ namespace DotNetCore.CAP | |||||
_compositeTask = Task.CompletedTask; | _compositeTask = Task.CompletedTask; | ||||
} | } | ||||
public void ReStart(bool force = false) | |||||
{ | |||||
if (!IsHealthy() || force) | |||||
{ | |||||
Pulse(); | |||||
_isHealthy = true; | |||||
Start(); | |||||
} | |||||
} | |||||
public void Dispose() | public void Dispose() | ||||
{ | { | ||||
if (_disposed) | if (_disposed) | ||||
@@ -79,9 +110,11 @@ namespace DotNetCore.CAP | |||||
} | } | ||||
_disposed = true; | _disposed = true; | ||||
_cts.Cancel(); | |||||
try | try | ||||
{ | { | ||||
Pulse(); | |||||
_compositeTask?.Wait(TimeSpan.FromSeconds(2)); | _compositeTask?.Wait(TimeSpan.FromSeconds(2)); | ||||
} | } | ||||
catch (AggregateException ex) | catch (AggregateException ex) | ||||
@@ -96,7 +129,7 @@ namespace DotNetCore.CAP | |||||
public void Pulse() | public void Pulse() | ||||
{ | { | ||||
//ignore | |||||
_cts?.Cancel(); | |||||
} | } | ||||
private void RegisterMessageProcessor(IConsumerClient client) | private void RegisterMessageProcessor(IConsumerClient client) | ||||
@@ -174,7 +207,7 @@ namespace DotNetCore.CAP | |||||
private void StoreMessage(CapReceivedMessage receivedMessage) | private void StoreMessage(CapReceivedMessage receivedMessage) | ||||
{ | { | ||||
_connection.StoreReceivedMessage(receivedMessage); | |||||
_connection.StoreReceivedMessage(receivedMessage); | |||||
} | } | ||||
private (Guid, string) TracingBefore(string topic, string values) | private (Guid, string) TracingBefore(string topic, string values) |
@@ -6,7 +6,10 @@ namespace DotNetCore.CAP | |||||
/// <summary> | /// <summary> | ||||
/// Handler received message of subscribed. | /// Handler received message of subscribed. | ||||
/// </summary> | /// </summary> | ||||
public interface IConsumerHandler : IProcessingServer | |||||
public interface IConsumerRegister : IProcessingServer | |||||
{ | { | ||||
bool IsHealthy(); | |||||
void ReStart(bool force = false); | |||||
} | } | ||||
} | } |
@@ -93,6 +93,7 @@ namespace DotNetCore.CAP.Processor | |||||
{ | { | ||||
var returnedProcessors = new List<IProcessor> | var returnedProcessors = new List<IProcessor> | ||||
{ | { | ||||
_provider.GetRequiredService<TransportCheckProcessor>(), | |||||
_provider.GetRequiredService<NeedRetryMessageProcessor>(), | _provider.GetRequiredService<NeedRetryMessageProcessor>(), | ||||
_provider.GetRequiredService<ICollectProcessor>() | _provider.GetRequiredService<ICollectProcessor>() | ||||
}; | }; | ||||
@@ -0,0 +1,35 @@ | |||||
// Copyright (c) .NET Core Community. All rights reserved. | |||||
// Licensed under the MIT License. See License.txt in the project root for license information. | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
namespace DotNetCore.CAP.Processor | |||||
{ | |||||
public class TransportCheckProcessor : IProcessor | |||||
{ | |||||
private readonly IConsumerRegister _register; | |||||
private readonly TimeSpan _waitingInterval; | |||||
public TransportCheckProcessor(IConsumerRegister register) | |||||
{ | |||||
_register = register; | |||||
_waitingInterval = TimeSpan.FromSeconds(30); | |||||
} | |||||
public async Task ProcessAsync(ProcessingContext context) | |||||
{ | |||||
if (context == null) | |||||
{ | |||||
throw new ArgumentNullException(nameof(context)); | |||||
} | |||||
if (!_register.IsHealthy()) | |||||
{ | |||||
_register.ReStart(); | |||||
} | |||||
await context.WaitAsync(_waitingInterval); | |||||
} | |||||
} | |||||
} |