1
votes

I attempted to port a working C# sample to an OOP version of F#.

Remote actors (on a separate process) are not receiving messages.

I receive the following error:

[ERROR][3/23/2017 4:39:10 PM][Thread 0008][[akka://system2/system/endpointManage
r/reliableEndpointWriter-akka.tcp%3A%2F%2Fsystem1%40localhost%3A8090-1/endpointW
riter#1919547364]] AssociationError [akka.tcp://system2@localhost:8080] <- akka.
tcp://system1@localhost:8090: Error [Object reference not set to an instance of
an object.] [   at Akka.Serialization.Serialization.FindSerializerForType(Type o
bjectType)
   at Akka.Remote.Serialization.DaemonMsgCreateSerializer.GetArgs(DaemonMsgCreat
eData proto)
   at Akka.Remote.Serialization.DaemonMsgCreateSerializer.FromBinary(Byte[] byte
s, Type type)
   at Akka.Serialization.Serialization.Deserialize(Byte[] bytes, Int32 serialize
rId, String manifest)

Here's the working C# version:

using (var system = ActorSystem.Create("system1", config))
{
    var reply = system.ActorOf<ReplyActor>("reply");

    //create a remote deployed actor
    var remote1 = system.ActorOf(Props.Create(() => new SomeActor()).WithRouter(FromConfig.Instance), "remoteactor1");
    var remote2 = system.ActorOf(Props.Create(() => new SomeActor()).WithRouter(FromConfig.Instance), "remoteactor2");
    var remote3 = system.ActorOf(Props.Create(() => new SomeActor()).WithRouter(FromConfig.Instance), "remoteactor3");

    var hashGroup = system.ActorOf(Props.Empty.WithRouter(new ConsistentHashingGroup(config)));

    Task.Delay(500).Wait();

    var routee1 = Routee.FromActorRef(remote1);
    hashGroup.Tell(new AddRoutee(routee1));

    var routee2 = Routee.FromActorRef(remote2);
    hashGroup.Tell(new AddRoutee(routee2));

    var routee3 = Routee.FromActorRef(remote3);
    hashGroup.Tell(new AddRoutee(routee3));

    Task.Delay(500).Wait();

    for (var i = 0; i < 5; i++)
    {
        for (var j = 0; j < 7; j++)
        {
            var message = new SomeMessage(j, $"remote message: {j}");
            hashGroup.Tell(message, reply);
        }
    }

    Console.ReadLine();
}

Here's the port to F# using OOP:

use system = ActorSystem.Create("system1", config)
let reply = system.ActorOf<ReplyActor>("reply")

let props1 = Props.Create(fun () -> SomeActor() :> obj)
let props2 = Props.Create(fun () -> SomeActor() :> obj)
let props3 = Props.Create(fun () -> SomeActor() :> obj)

let remote1 = system.ActorOf(props1.WithRouter(FromConfig.Instance), "remoteactor1")
let remote2 = system.ActorOf(props2.WithRouter(FromConfig.Instance), "remoteactor2")
let remote3 = system.ActorOf(props3.WithRouter(FromConfig.Instance), "remoteactor3")

let hashGroup = system.ActorOf(Props.Empty.WithRouter(ConsistentHashingGroup(config)))
Task.Delay(500).Wait();

let routee1 = Routee.FromActorRef(remote1);
hashGroup.Tell(new AddRoutee(routee1));

let routee2 = Routee.FromActorRef(remote2);
hashGroup.Tell(new AddRoutee(routee2));

let routee3 = Routee.FromActorRef(remote3);
hashGroup.Tell(new AddRoutee(routee3));

Task.Delay(500).Wait();

for i = 0 to 5 do
    for j = 0 to 7 do

        let message = new HashMessage(j, sprintf "remote message: %i" j);
        hashGroup.Tell(message, reply);

Console.ReadLine() |> ignore

Question:

Am I suppose to upcast SomeActor to the object type when invoking the Props.Create method?

let props1 = Props.Create(fun () -> SomeActor() :> obj)
let props2 = Props.Create(fun () -> SomeActor() :> obj)
let props3 = Props.Create(fun () -> SomeActor() :> obj)

The code above is the only difference that I'm aware of.

The only other difference is the tcp path.

C#'s TCP:

remote {
    dot-netty.tcp {
        port = 8090
        hostname = localhost
    }

F#'s TCP:

    remote {
        helios.tcp {
            port = 8090
            hostname = localhost
        }
1
Not sure if it's a material difference, but your for loops are 1 element bigger in F# (since you check for < rather than <= in C#).kvb
Thank you for that observation kvb.Scott Nimrod
What happens if you don't upcast to obj? It looks like there are a bunch of overrides of Create, so I wouldn't be surprised if the upcast were forcing a different one to be used, which could explain the results.kvb
Unable to cast object of type 'System.Linq.Expressions.InstanceMethodCallExpressionN' to type 'System.Linq.Expressions.NewExpression'.Scott Nimrod
Then I think you'll need to use another overload - when F# creates C#-style LINQ expression trees it sometimes adds some additional cruft, but in this case the akka.net implementation expects a quotation with a very specific shape. Looking at the implementation, it seems like you could call Props.Create<SomeActor>() instead.kvb

1 Answers

3
votes

Props object is a descriptor for the creation process of target actor. Moreover, it must be serializable, as sometimes it may get included on messages passed through the network.

In order to work this way Props internally describes actor construction in form of (actor-type, actor-constructor-arguments). Props.Create(() => new Actor()) is only a helper here: what it actually does, is deconstruct the constructor expression into type info with arguments. This is why it works only with new Actor() expressions.

The problem with your F# code is that you're defining actor creation as a F# function, which props deconstructor doesn't know how to handle. You may still want create your actors using:

Props.Create(typeof<Actor>, [| arg1; arg2 |]) 

but then you need to keep the correctness of the constructor params by yourself. You can also use i.e. Akkling with it's typed version of props.