Search code examples
f#akka.netakka.net-cluster

How to implement Props.Create using F#


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
        }

Solution

  • 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.