I'm creating retry/reconnect functionality for a actor that is on a remote service. The actor should on prestart call the selection reference to subscribe to messages from the actor on the remote service. If for some reason the actor is not there I want my actor to retry a couple of times before reporting a failure to connect.
I'm using the TestKit to tdd the functionality. The problem I'm running into is that when the actor throws the timeout exception prestart is called afterwards even though I have a stop strategy.
I'm I missunderstanding the lifecycle of the actor? Seems wierd that PreStart is called if the strategy is to stop after failure.
public class MyActor : ReceiveActor
{
private readonly ActorPath path;
private ActorSelection selection;
private int retry = 0;
protected override void PreStart()
{
selection = Context.ActorSelection(path);
selection.Tell("prestart");
SetReceiveTimeout(TimeSpan.FromSeconds(1));
}
public MyActor(ActorPath path)
{
this.path = path;
Receive<ReceiveTimeout>(timeout =>
{
if (retry < 2)
{
retry++;
selection.Tell("retry");
return;
}
throw new Exception("timeout");
});
}
}
public class Test : TestKit
{
[Fact]
public void FactMethodName()
{
var probe = CreateTestProbe();
var props = Props.Create(() => new MyActor(probe.Ref.Path))
.WithSupervisorStrategy(new OneForOneStrategy(exception => Directive.Stop));
Sys.ActorOf(props);
//Initial
probe.ExpectMsg<string>(s => s=="prestart",TimeSpan.FromSeconds(2));
//Retries
probe.ExpectMsg<string>(s => s == "retry", TimeSpan.FromSeconds(2));
probe.ExpectMsg<string>(s => s == "retry", TimeSpan.FromSeconds(2));
//No more
probe.ExpectNoMsg( TimeSpan.FromSeconds(10));
}
}
My Solution after answer from Jeff
public class ParentActor : UntypedActor
{
private readonly Func<IUntypedActorContext, IActorRef> creation;
private IActorRef actorRef;
public ParentActor(Func<IUntypedActorContext, IActorRef> creation)
{
this.creation = creation;
}
protected override void PreStart()
{
actorRef = creation(Context);
}
protected override void OnReceive(object message)
{
actorRef.Tell(message);
}
}
public class MyActor : ReceiveActor
{
private readonly ActorPath path;
private int retry;
private ActorSelection selection;
public MyActor(ActorPath path)
{
this.path = path;
Receive<ReceiveTimeout>(timeout =>
{
if (retry < 2)
{
retry++;
selection.Tell("retry");
return;
}
throw new Exception("timeout");
});
}
protected override void PreStart()
{
selection = Context.ActorSelection(path);
selection.Tell("prestart");
SetReceiveTimeout(TimeSpan.FromSeconds(1));
}
}
public class Test : TestKit
{
[Fact]
public void FactMethodName()
{
var probe = CreateTestProbe();
var props = Props.Create(
() => new ParentActor(context => context.ActorOf(Props.Create(
() => new MyActor(probe.Ref.Path), null), "myactor")))
.WithSupervisorStrategy(new OneForOneStrategy(exception => Directive.Stop));
Sys.ActorOf(props);
//Initial
probe.ExpectMsg<string>(s => s == "prestart", TimeSpan.FromSeconds(2));
//Retries
probe.ExpectMsg<string>(s => s == "retry", TimeSpan.FromSeconds(2));
probe.ExpectMsg<string>(s => s == "retry", TimeSpan.FromSeconds(2));
//No more
probe.ExpectNoMsg(TimeSpan.FromSeconds(10));
}
}
Works but feels like it could be a part of the TestKit to beable set supervisor strategy for the guardian of the test
var props = Props.Create(() => new MyActor(probe.Ref.Path))
.WithSupervisorStrategy(new OneForOneStrategy(exception => Directive.Stop));
This set the supervisor strategy for MyActor, it will be used for MyActor's children and not for MyActor itself.
Sys.ActorOf(props);
This is creating MyActor under the /user guardian which have a OneForOne Restart directive by default. This is why your actor is restarting.
To get what you want, you need to create a parent actor with the custom supervisor strategy and create MyActor under it.
Related
Masstransit, but I have such a situation that several message consumers can be launched on one queue, I created a filter that could help me receive the necessary messages for those producers who made the request, but there is a problem that after the message gets into the filter it is marked as read.
Is it possible to make it so that after hitting the filter and its unsuccessful passage, the message remains in the queue.
public class FilterConsumer <TConsumer, TMessage>: IFilter <ConsumerConsumeContext <TConsumer, TMessage>>
where TConsumer: class
where TMessage: class, ICacheKey {
private readonly MemoryCacheHelper _cache;
public FilterConsumer(IMemoryCache cache) {
_cache = new MemoryCacheHelper(cache);
}
public void Probe(ProbeContext context) {
context.CreateFilterScope("filterConsumer");
context.Add("output", "console");
}
public async Task Send(ConsumerConsumeContext <TConsumer, TMessage> context, IPipe <ConsumerConsumeContext < TConsumer, TMessage>> next) {
Console.WriteLine(context.Message.CacheKey());
if (_cache.CheckCache(context.Message.CacheKey()))
await next.Send(context);
else
await context.NotifyConsumed(TimeSpan.Zero, $ "Filtered");
}
}
public class AccountsConsumerDefinition: ConsumerDefinition < AccountsConsumer > {
private readonly IMemoryCache _cache;
public AccountsConsumerDefinition(IMemoryCache cache) {
_cache = cache;
}
protected override void ConfigureConsumer(IReceiveEndpointConfigurator endpointConfigurator, IConsumerConfigurator <AccountsConsumer> consumerConfigurator) {
consumerConfigurator.ConsumerMessage <AccountsBusResponse> (m => m.UseFilter(new FilterConsumer <AccountsConsumer, AccountsBusResponse> (_cache)));
}
}
services.AddMassTransit < TBus > (x => {
if (consumer != null)
x.AddConsumer < TConsumer > (consumerDifinition);
}
I have .Net core 5.0 api and I'm trying to add logs but somehow its not working. below is my setup.
public void ConfigureServices(IServiceCollection services)
{
ConfigureRollbarSingleton();
services.AddControllers();
services.AddDbContext<AfterSchoolContext>(options => options.UseSqlServer(connectionString: connectionString, m => m.MigrationsAssembly("AfterSchoolHQ")));
services.AddScoped<IRepositoryWrapper, RepositoryWrapper>();
// Automapper Configuration
services.AddAutoMapper(typeof(Startup));
services.AddHttpContextAccessor();
services.AddRollbarLogger(loggerOptions =>
{
loggerOptions.Filter =
(loggerName, loglevel) => loglevel >= LogLevel.Trace;
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "AfterSchoolHQ", Version = "v1" });
});
}
private void ConfigureRollbarSingleton()
{
RollbarLocator.RollbarInstance
// minimally required Rollbar configuration:
.Configure(new RollbarConfig(rollbarAccessToken) { Environment = rollbarEnvironment })
// optional step if you would like to monitor
// Rollbar internal events within your application:
.InternalEvent += OnRollbarInternalEvent
;
// Optional info about reporting Rollbar user:
SetRollbarReportingUser("007", "jbond#mi6.uk", "JBOND");
}
And here is my controller.
public class TestController : ControllerBase
{
private readonly IRepositoryWrapper _repositories;
private readonly Rollbar.ILogger _logger;
private readonly IMapper _mapper;
public TestController(Rollbar.ILogger logger, IMapper mapper, IRepositoryWrapper repositories)
{
_mapper = mapper;
_logger = logger;
_repositories = repositories;
}
[HttpGet("GetByID")]
public IActionResult GetById(int id)
{
try
{
if (id <= 0)
return new NotFoundResult();
_logger.Info("test"); //I'm trying to add a log from here
var request = _repositories.GetById(id: id);
if (request == null)
return new NotFoundResult();
return new OkObjectResult(_mapper.Map(source: request));
}
catch (Exception ex)
{
return new ObjectResult(ex.Message) { StatusCode = 500 };
}
}
It gives me error that 'unable to resolve service for type Rollbar.ILogger'. I don't even see a way in any docs to handle this cases. any help would be really appreciated.
Make sure you are using Rollbar.NetCore.AspNet Nuget package v5.0.0-beta and newer. For example: https://www.nuget.org/packages/Rollbar.NetCore.AspNet/5.0.4-beta
and follow this instructions:
https://docs.rollbar.com/docs/v5-integrating-with-aspnet-core-2-and-newer
If that does not help, please, open an issue at:
https://github.com/rollbar/Rollbar.NET/issues
I'll follow up there...
I have a consumer that is also publishing a response back to the bus. I can get an IReceiveObserver wired up and working on the bus, but I haven't been able to get either an ISendObserver or IPublishObserver running. I have confirmed with RabbitMQ management console that the messages are being published correctly.
class Program
{
static BusHandle _BusHandle;
static void Main(string[] args)
{
InitLogging();
InitStructureMap();
InitBus();
System.Console.WriteLine("Starting processing, ENTER to stop...");
System.Console.ReadLine();
System.Console.WriteLine("See you later, alligator!");
StopBus();
}
static void InitBus()
{
var busCtrl = ObjectFactory.Container.GetInstance<IBusControl>();
var recObserver = ObjectFactory.Container.GetInstance<IReceiveObserver>();
var sendObserver = ObjectFactory.Container.GetInstance<ISendObserver>();
busCtrl.ConnectReceiveObserver(recObserver);
busCtrl.ConnectSendObserver(sendObserver);
_BusHandle = busCtrl.Start();
}
static void StopBus()
{
_BusHandle.Stop();
}
static void InitLogging()
{
XmlConfigurator.Configure();
Log4NetLogger.Use();
}
static void InitStructureMap()
{
ObjectFactory.Initialize(x => {
x.AddRegistry<MyTestConsoleRegistry>();
x.AddRegistry<MyTestRegistry>();
});
}
}
public class MyTestConsoleRegistry : Registry
{
public MyTestConsoleRegistry()
{
var rabbitURI = ConfigurationManager.AppSettings["rabbitMQHostUri"];
var queueName = ConfigurationManager.AppSettings["massTransitQueue"];
For<IBusControl>(new SingletonLifecycle())
.Use("Configure IBusControl for MassTransit consumers with RabbitMQ transport",
ctx => Bus.Factory.CreateUsingRabbitMq(cfg => {
cfg.UseJsonSerializer();
cfg.PublisherConfirmation = true;
var host = cfg.Host(new Uri(rabbitURI), rabbitCfg => { });
cfg.ReceiveEndpoint(host, queueName, endpointCfg => {
endpointCfg.LoadFrom(ctx);
});
})
);
For<IReceiveObserver>().Use<MassTransitObserver>();
For<ISendObserver>().Use<MassTransitObserver>();
// ...snip...
}
}
public class MyTestRegistry : Registry
{
public MyTestRegistry()
{
ForConcreteType<MyTestConsumer>();
// ...snip...
}
}
public class MassTransitObserver : IReceiveObserver, ISendObserver
{
// Does nothing for now, just trying to wire it up...
public Task ConsumeFault<T>(ConsumeContext<T> context, TimeSpan duration, string consumerType, Exception exception) where T : class
{
return Task.CompletedTask;
}
public Task PostConsume<T>(ConsumeContext<T> context, TimeSpan duration, string consumerType) where T : class
{
return Task.CompletedTask;
}
public Task PostReceive(ReceiveContext context)
{
return Task.CompletedTask;
}
public Task PreReceive(ReceiveContext context)
{
return Task.CompletedTask;
}
public Task ReceiveFault(ReceiveContext context, Exception exception)
{
return Task.CompletedTask;
}
public Task PreSend<T>(SendContext<T> context) where T : class
{
return Task.CompletedTask;
}
public Task PostSend<T>(SendContext<T> context) where T : class
{
return Task.CompletedTask;
}
public Task SendFault<T>(SendContext<T> context, Exception exception) where T : class
{
return Task.CompletedTask;
}
}
public class MyTestConsumer : IConsumer<MyTestMessage>,
// for testing only:
IConsumer<MyTestResponse>
{
readonly IDoSomething _DoSomething;
public TestConsumer(IDoSomething doSomething)
{
_DoSomething = doSomething;
}
public Task Consume(ConsumeContext<MyTestResponse> context)
{
// For testing only...
return Task.CompletedTask;
}
public async Task Consume(ConsumeContext<MyTestMessage> context)
{
var result = await _DoSomething(context.Message.Id);
var resp = new MyTestResponseMessage(result);
await context
.Publish<MyTestResponse>(resp);
}
}
Given this code, the IReceiveObserver methods are getting called, but the ISendObserver methods are not.
I'm new to MassTransit, I expect this is probably a straightforward issue.
EDIT: A unit test using NUnit and Moq, doesn't use StructureMap. I believe this properly illustrates what I'm seeing.
[Test]
public void TestSendObserver()
{
var bus = CreateBus();
var busHandle = bus.Start();
var sendObs = new Mock<ISendObserver>();
sendObs.Setup(x => x.PreSend<TestMessage>(It.IsAny<SendContext<TestMessage>>()))
.Returns(Task.FromResult(0))
.Verifiable();
sendObs.Setup(x => x.PostSend<TestMessage>(It.IsAny<SendContext<TestMessage>>()))
.Returns(Task.FromResult(0))
.Verifiable();
using (bus.ConnectSendObserver(sendObs.Object)) {
var pubTask = bus.Publish(new TestMessage { Message = "Some test message" });
pubTask.Wait();
}
busHandle.Stop();
// Fails, neither PreSend nor PostSend have been called
sendObs.Verify(x => x.PreSend<TestMessage>(It.IsAny<SendContext<TestMessage>>()), Times.Once());
sendObs.Verify(x => x.PostSend<TestMessage>(It.IsAny<SendContext<TestMessage>>()), Times.Once());
}
IBusControl CreateBus()
{
return MassTransit.Bus.Factory.CreateUsingRabbitMq(x => {
var host = x.Host(new Uri("rabbitmq://localhost/"), h => {
h.Username("guest");
h.Password("guest");
});
});
}
public class TestMessage
{
public String Message { get; set; }
}
Trying to model a system sending out notifications from a number of publishers using RX.
I have two custom interfaces ITopicObservable and ITopicObserver to model the fact that the implementing classes will have other properties and methods apart from the IObservable and IObserver interfaces.
The problem I have is that my thinking is I should be able to add a number of observables together, merge them together and subscribe to an observer to provide updates from all merged observables. However the code with "the issue" comment throws an invalid cast exception.
The use case is a number of independent sensors each monitoring a temperature in a box for example that aggregate all their reports to one temperature report which is then subscribed to by a temperature health monitor.
What am I missing here? Or is there a better way to implement the scenario using RX?
Code below
using System;
using System.Reactive.Linq;
using System.Collections.Generic;
namespace test
{
class MainClass
{
public static void Main (string[] args)
{
Console.WriteLine ("Hello World!");
var to = new TopicObserver ();
var s = new TopicObservable ("test");
var agg = new AggregatedTopicObservable ();
agg.Add (s);
agg.Subscribe (to);
}
}
public interface ITopicObservable<TType>:IObservable<TType>
{
string Name{get;}
}
public class TopicObservable:ITopicObservable<int>
{
public TopicObservable(string name)
{
Name = name;
}
#region IObservable implementation
public IDisposable Subscribe (IObserver<int> observer)
{
return null;
}
#endregion
#region ITopicObservable implementation
public string Name { get;private set;}
#endregion
}
public class AggregatedTopicObservable:ITopicObservable<int>
{
List<TopicObservable> _topics;
private ITopicObservable<int> _observable;
private IDisposable _disposable;
public AggregatedTopicObservable()
{
_topics = new List<TopicObservable>();
}
public void Add(ITopicObservable<int> observable)
{
_topics.Add ((TopicObservable)observable);
}
#region IObservable implementation
public IDisposable Subscribe (IObserver<int> observer)
{
_observable = (ITopicObservable<int>)_topics.Merge ();
_disposable = _observable.Subscribe(observer);
return _disposable;
}
#endregion
#region ITopicObservable implementation
public string Name { get;private set;}
#endregion
}
public interface ITopicObserver<TType>:IObserver<TType>
{
string Name{get;}
}
public class TopicObserver:ITopicObserver<int>
{
#region IObserver implementation
public void OnNext (int value)
{
Console.WriteLine ("next {0}", value);
}
public void OnError (Exception error)
{
Console.WriteLine ("error {0}", error.Message);
}
public void OnCompleted ()
{
Console.WriteLine ("finished");
}
#endregion
#region ITopicObserver implementation
public string Name { get;private set;}
#endregion
}
}
My first thought, is that you shouldn't implement IObservable<T>, you should compose it by exposing it as a property or the result of a method.
Second thought is that there are operators in Rx that excel at merging/aggregating multiple sequences together.
You should favor using those.
Third, which is similar to the first, you generally don't implement IObserver<T>, you just subscribe to the observable sequence and provide delegates for each call back (OnNext, OnError and OnComplete)
So your code basically is reduced to
Console.WriteLine("Hello World!");
var topic1 = TopicListener("test1");
var topic2 = TopicListener("test2");
topic1.Merge(topic2)
.Subscribe(
val => { Console.WriteLine("One of the topics published this value {0}", val);},
ex => { Console.WriteLine("One of the topics errored. Now the whole sequence is dead {0}", ex);},
() => {Console.WriteLine("All topics have completed.");});
Where TopicListener(string) is just a method that returns IObservable<T>.
The implementation of the TopicListener(string) method would most probably use Observable.Create.
It may help to see examples of mapping Rx over a Topic based messaging system.
There is an example of how you can layer Rx over TibRv topics here https://github.com/LeeCampbell/RxCookbook/blob/master/IO/Comms/TibRvSample.linq
The signature of the .Merge(...) operator that you're using is:
IObservable<TSource> Merge<TSource>(this IEnumerable<IObservable<TSource>> sources)
The actual type returned by this .Merge() is:
System.Reactive.Linq.ObservableImpl.Merge`1[System.Int32]
...so it should be fairly clear that calling (ITopicObservable<int>)_topics.Merge(); would fail.
Lee's advice not to implement either of IObservable<> or IObserver<> is the correct one. It leads to errors like the one above.
If you had to do something like this, I would do it this way:
public interface ITopic
{
string Name { get; }
}
public interface ITopicObservable<TType> : ITopic, IObservable<TType>
{ }
public interface ITopicSubject<TType> : ISubject<TType>, ITopicObservable<TType>
{ }
public interface ITopicObserver<TType> : ITopic, IObserver<TType>
{ }
public class Topic
{
public string Name { get; private set; }
public Topic(string name)
{
this.Name = name;
}
}
public class TopicSubject : Topic, ITopicSubject<int>
{
private Subject<int> _subject = new Subject<int>();
public TopicSubject(string name)
: base(name)
{ }
public IDisposable Subscribe(IObserver<int> observer)
{
return _subject.Subscribe(observer);
}
public void OnNext(int value)
{
_subject.OnNext(value);
}
public void OnError(Exception error)
{
_subject.OnError(error);
}
public void OnCompleted()
{
_subject.OnCompleted();
}
}
public class AggregatedTopicObservable : Topic, ITopicObservable<int>
{
List<ITopicObservable<int>> _topics = new List<ITopicObservable<int>>();
public AggregatedTopicObservable(string name)
: base(name)
{ }
public void Add(ITopicObservable<int> observable)
{
_topics.Add(observable);
}
public IDisposable Subscribe(IObserver<int> observer)
{
return _topics.Merge().Subscribe(observer);
}
}
public class TopicObserver : Topic, ITopicObserver<int>
{
private IObserver<int> _observer;
public TopicObserver(string name)
: base(name)
{
_observer =
Observer
.Create<int>(
value => Console.WriteLine("next {0}", value),
error => Console.WriteLine("error {0}", error.Message),
() => Console.WriteLine("finished"));
}
public void OnNext(int value)
{
_observer.OnNext(value);
}
public void OnError(Exception error)
{
_observer.OnError(error);
}
public void OnCompleted()
{
_observer.OnCompleted();
}
}
And run it with:
var to = new TopicObserver("watching");
var ts1 = new TopicSubject("topic 1");
var ts2 = new TopicSubject("topic 2");
var agg = new AggregatedTopicObservable("agg");
agg.Add(ts1);
agg.Add(ts2);
agg.Subscribe(to);
ts1.OnNext(42);
ts1.OnCompleted();
ts2.OnNext(1);
ts2.OnCompleted();
Which gives:
next 42
next 1
finished
But apart from being able to give everything a name (which I'm not sure how it helps) you could always do this:
var to =
Observer
.Create<int>(
value => Console.WriteLine("next {0}", value),
error => Console.WriteLine("error {0}", error.Message),
() => Console.WriteLine("finished"));
var ts1 = new Subject<int>();
var ts2 = new Subject<int>();
var agg = new [] { ts1, ts2 }.Merge();
agg.Subscribe(to);
ts1.OnNext(42);
ts1.OnCompleted();
ts2.OnNext(1);
ts2.OnCompleted();
Same output with no interfaces and classes.
There's even a more interesting way. Try this:
var to =
Observer
.Create<int>(
value => Console.WriteLine("next {0}", value),
error => Console.WriteLine("error {0}", error.Message),
() => Console.WriteLine("finished"));
var agg = new Subject<IObservable<int>>();
agg.Merge().Subscribe(to);
var ts1 = new Subject<int>();
var ts2 = new Subject<int>();
agg.OnNext(ts1);
agg.OnNext(ts2);
ts1.OnNext(42);
ts1.OnCompleted();
ts2.OnNext(1);
ts2.OnCompleted();
var ts3 = new Subject<int>();
agg.OnNext(ts3);
ts3.OnNext(99);
ts3.OnCompleted();
This produces:
next 42
next 1
next 99
It allows you to add new source observables after the merge!
This is what I have thus far. I'm stuck on the concept/where to implement the IBus injection/creation point (the publisher). I was trying to keep the publisher functionality all within the project and not create a separate service for it.
bus = RabbitHutch.CreateBus("host=localhost", x => x.Register<IEasyNetQLogger>(_ => logger));
This is my first pass at this so I'm open to suggestions and best practice advice :-)
Things left to do:
create a rabbitmq config file with proper settings defining queue name and ?
create a message handler that takes care of connection management that is?
create the publisher at app start up, dispose properly when ?
EasyNetQ Wrapper to replace EasyNetQ internal IoC, Ninject replacement:
public class NinjectContainerWrapper: IContainer, IDisposable
{
public NinjectContainerWrapper()
{
}
//ninject container/kernal? here
//private readonly ISomeNinjectInterface container;
public TService Resolve<TService>() where TService : class
{
throw new NotImplementedException();
}
public IServiceRegister Register<TService, TImplementation>()
where TService : class
where TImplementation : class, TService
{
throw new NotImplementedException();
}
public IServiceRegister Register<TService>(Func<EasyNetQ.IServiceProvider, TService> serviceCreator) where TService : class
{
throw new NotImplementedException();
}
public void Dispose()
{
throw new NotImplementedException();
}
}
NinjectWebCommon.cs
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IAppSettings>().To<AppSettings>();
kernel.Bind<IDmpeService>().To<DmpeService>();
kernel.Bind<IPublisher>().To<DmpePublisher>();
kernel.Bind<IEasyNetQLogger>().To<GdmEasyNetQLogger>();
kernel.Bind<ILoggingService>().ToMethod(x =>
{
var scope = x.Request.ParentRequest.Service.FullName;
var log = (ILoggingService)LogManager.GetLogger(scope, typeof(LoggingService));
return log;
});
}
the publisher class:
public class DmpePublisher: IPublisher
{
private readonly IEasyNetQLogger _logger;
private readonly IAppSettings _appSettings;
private readonly IBus bus = null;
public DmpePublisher(IEasyNetQLogger logger, IAppSettings appSettings)
{
this._logger = logger;
this._appSettings = appSettings;
// register our alternative container factory
RabbitHutch.SetContainerFactory(() =>
{
var ninjectContainer = new NinjectContainerWrapper();
// wrap it in our implementation of EasyNetQ.IContainer
//return new NinjectContainerWrapper(ninjectContainer);
});
bus = RabbitHutch.CreateBus("host=localhost", x => x.Register<IEasyNetQLogger>(_ => logger));
}
public void PublishMessage(Messages.IMessage message)
{
throw new NotImplementedException();
//log post
//_logger.InfoWrite("Publishing message: {0}", message);
}
}