Disclaimer: I'm new to Akka :)
I'm trying to implement a router in Akka, that basically
- receives a message
- looks up in dictionary for IActorRef that handle type of message
- if no match is found, create one using Akka.DI as child actor and add to dictionary
- forward message to that actor
This works great - the first time, but if I try to Tell() or Ask() the router twice, the second message always ends up in the stream as unhandled
I've tried overriding Unhandled() in the child actor and putting a breakpoint there, and that is in fact being hit on the second message.
Router:
public class CommandRouter : UntypedActor
{
protected readonly IActorResolver _resolver;
private static readonly Dictionary<Type, IActorRef> _routees = new Dictionary<Type, IActorRef>();
private ILoggingAdapter _log = Context.GetLogger(new SerilogLogMessageFormatter());
public CommandRouter(IActorResolver resolver)
{
_resolver = resolver;
}
protected override void OnReceive(object message)
{
_log.Info("Routing command {cmd}", message);
var typeKey = message.GetType();
if (!_routees.ContainsKey(typeKey))
{
var props = CreateActorProps(typeKey);
if (!props.Any())
{
Sender?.Tell(Response.WithException(
new RoutingException(
$"Could not route message to routee. No routees found for message type {typeKey.FullName}")));
return;
}
if (props.Count() > 1)
{
Sender?.Tell(Response.WithException(
new RoutingException(
$"Multiple routees registered for message {typeKey.FullName}, which is not supported by this router. Did you want to publish stuff instead?")));
return;
}
var prop = props.First();
var routee = Context.ActorOf(prop, prop.Type.Name);
_routees.Add(typeKey, routee);
}
_routees[typeKey].Forward(message);
}
private IEnumerable<Props> CreateActorProps(Type messageType)
{
return _resolver.TryCreateActorProps(typeof(IHandleCommand<>).MakeGenericType(messageType)).ToList();
}
protected override SupervisorStrategy SupervisorStrategy()
{
return new OneForOneStrategy(x => Directive.Restart);
}
}
The ActorResolver-method, which uses the DependencyResolver from Akka.DI.StructureMap:
public IEnumerable<Props> TryCreateActorProps(Type actorType)
{
foreach (var type in _container.GetAllInstances(actorType))
{
yield return _resolver.Create(type.GetType());
}
}
The actual child actor is quite straigt forward:
public class ProductSubscriptionHandler : ReceiveActor, IHandleCommand<AddProductSubscription>
{
public ProductSubscriptionHandler()
{
Receive<AddProductSubscription>(Handle);
}
protected bool Handle(AddProductSubscription command)
{
Sender?.Tell(Response.Empty);
return true;
}
}
The whole thing is called after the actor system has initialized, like so:
var router = Sys.ActorOf(resolver.Create<CommandRouter>(), ActorNames.CommandRouter);
router.Ask(new AddProductSubscription());
router.Ask(new AddProductSubscription());
I consistently get this error on the second (or any subsequent) message: "Unhandled message from...":
[INFO][17-07-2017 23:05:39][Thread 0003][[akka://pos-system/user/CommandRouter#676182398]] Routing command Commands.AddProductSubscription
[DEBUG][17-07-2017 23:05:39][Thread 0003][akka://pos-system/user/CommandRouter] now supervising akka://pos-system/user/CommandRouter/ProductSubscriptionHandler
[DEBUG][17-07-2017 23:05:39][Thread 0003][akka://pos-system/user/CommandRouter] *Unhandled message from akka://pos-system/temp/d* : Documents.Commands.AddProductSubscription
[DEBUG][17-07-2017 23:05:39][Thread 0007][akka://pos-system/user/CommandRouter/ProductSubscriptionHandler] Started (Consumers.Service.Commands.ProductSubscriptionHandler)