It's from one of the main authors of Akka.Net, so it should deserve a good amount of trust. Although Akka.Net was of course in main ways a quite straightforward port of the Java code, which means the design credit there goes to the original Akka developers.
Founder of both Akka.NET and Proto.Actor here.
Right now, Proto.Actor-dotnet should not be trusted more than Akka.NET.
Akka.NET is production ready and has a huge community. has commercial support etc.
There are however a fair number of design issues that I dislike personally which led me to start Proto.Actor
The .NET implementation of proto.actor is in alpha version right now.
We are building and stabilizing the Go version first, which already is used in major production systems.
Roger, lurker on Akka.NET and about to start an Akka.NET PoC at work - proto.actor looks interesting - Can you elaborate on the design issues you have with Akka.NET?
Serialization and the concept of the ActorRef.
They both go hand in hand.
in Akka/NET, ActorRefs are contextual, they belong to a given ActorSystem.
This means that when they are serialized, the serializer needs to be able to know how to transfer and rebind this context on serialization and deserialization.
To make it more complex, ActorRefs can be embedded in other messages.
There are a few other primitives also that are contextual and serialized.
This limits the options you have in terms of serializers.
It also puts a massive tax on the framework in terms of maintenance.
ActorRefs are also "resolved" when deserialized, so the deserializing system has to figure out which actor is targeted.
This lookup is slow due to parsing and scanning of actors and their children.
This makes network communication speed suffer _a lot_.
The configuration DSL HOCON is also tightly integrated with the entire infrastructure, making it hard to reason about what is going on and what parts of the config the current piece of code actually sees.
Akka/NET also lacks good interception points, its hard to hook into actors and monitor them.
So the issues are really both on a performance level and at a maintenance level.
The mindset in Akka.NET have been more "lets build everything ourselves from scratch to match the JVM" and in proto.actor "lets re-use proven tech and glue them together, with minimal code"
.NET and Go are a weird pairing, especially since the library isn't actually "cross-platform." It's two separate implementations. I wonder what the impetus was to develop this library on those two particular platforms, especially since there's already Akka.NET and most people can't shut up about how much they love Goroutines as a concurrency primitive.
I have to imagine that the .NET application is client side and GO is used server side. .NET has released some distros that will release to Linux systems (though, to be honest, I have never actually tried implementation, so this is all conjecture =/ . Have they gone to Mac, at all? I really don't know. Java would seem a better choice, depending on what they are doing with it, but with Azure, maybe some back end systems run better using Azure and GO...... as you have surely noticed, by this point, I'm spitballing out of my realm, but it is a curious pairing, as you say.
I think for a communication framework cross-platform has a different meaning then for a simple program or library. I would interpret cross-platform here as "interoperable between platforms", or I can connect one Actor on platform A to another Actor on platform B. IMHO that's something really desireable, because it doesn't one to using a single programming language on a bigger distributed system.
We should be really skeptical of (or perhaps we could charitably say, "We should contextualize these,") speed claims. Akka does well in benchmarks too, until you put significant memory pressure on the VM and find it handles less gracefully under load than a more JVM-natural approach using approaches in java.util.concurrent.
Actor-based approaches with hundreds of thousands of actors are really amazing, powerful and often simpler than equivalent channel based systems, conceptually. But they require special GC considerations to be made "efficient" of a really high level of mechanical sympathy to make sure you aren't creating pathologically bad situations for the garbage collector.
My favorite conference talk is about the multiple facets of Erlang that combine to make it such a great language. Take an opinionated language, an opinionated VM, and a focus on a specific problem space, and magic happens.
Take one or two features from Erlang into another language, and you have meh.
Considering the production system that the Erlang VM has been deployed to(and the length of time it's existed and thrived) it's hard to make an argument to re-invent it in another language.
If that's what your domain space needs, use the BEAM and find a way to talk to it for the parts that you can't move to Erlang.
Sometimes the concurrency benefits just don't add up when compared to other business/project concerns.
I say this as an big fan of Erlang, truth is, sometimes you just have to accept that your job is to write a python/Django backend without a line of JavaScript in order to serve as a line of business app, and if the load gets bigger, you just throw another server at it.
That said, erlang is great, elixir is awesome, and both are 100% worth every developers time to learn.
Can I be honest? As a language, Erlang kinda is a cube of frozen barf. It's not messy, but it's certainly not delicious. I do not "love it." I think most people sort of work around this; OTP is so good you put up with a kinda ugly syntactic substrate. Ideas good, array syntax bad, etc.
>powerful and often simpler than equivalent channel based systems, conceptually
Without true experience with any of agents/channels, what make it simpler? I read the GO and compare with Actors in F# or Erlang and for me it easier to understand channels. Mainly because are typed on the data.
The thing is, Types are really hard to make work in a distributed unbounded non deterministic system like... well any system that is multi threaded. You can try to hide stuff with channels, but most of the time, you will feel the abstraction leaking.
I maybe can see if we are talking about distributed across machines, runtimes with different versions or languages, but not with multi threaded. Any example?
What is the difference between multithread and different machines? Time to get a synchronous response ? At best. But errors happens to and error situations are the same.
> Without true experience with any of agents/channels, what make it simpler?
If you are very clear about not retaining references to things you ship to another actor, Erlang is marvelously concurrent without any of the usual considerations for shared memory or functional programming we have to make. You can confidently manipulate and mutate variables in your actor scope because your actor is single threaded and OTP makes async-with-response calls trivial to mix in with this when you want function semantics.
Actor based systems give you a place to put sequential mutation code and a clear, clean, scalable way to define parallel operations. Channels really... well they give you this blunt force instrument to export data but you end up implementing actor-like management every time they're invoked if you're doing any sort of fan-out anyways.
It's great to see more actor runtimes on the rise. Also interesting to see the actor model implemented in Go, which follows more of a pi calculus design with its channels. With an actor design, the software architecture shifts towards the endpoints of the communication.
CAF targets the performance-sensitive C++ community, so sending a message in the same process only costs a single hardware-support CAS operation. We found this scales very well. No GC other than reference counting.
For those interested in learning more about actors in the C++ ecosystem, I recently gave a talk about CAF and some performance comparisons to other frameworks in various scenarios:
http://matthias.vallentin.net/slides/caf-rise.pdf
I've been using the CAF library for a while, and the experience so far has been very positive. It's surprisingly capable and flexible at the same time. The use of modern C++ in CAF makes your code very clean and idiomatic. Other C++ libraries of this kind are usually quite painful to use (because you know, most people think if it's C++ it has to be hard). CAF is a pleasant exception to this rule.
Most of my non trivial goroutines are organized like this:
func actor_foo(things) error {
set_up_stuff()
defer clean_up_stuff()
for {
select {
case msg := <-messages:
switch msg.(type) {
case *Bla:
}
case <- other_messages:
case <- errors:
case <- signals:
return nil
}
}
}
I recognize there is more to the actor model than just message loops, but I found that an explicit for-select-switch results in better overall readability.
the only challenge here is that upstream senders will block when your channel(s) are full. This is not inherently bad, but if you have a circular dependency, you can deadlock because queues (channels) are bounded and may fill up. There are solutions to that too, but you have to build that in
46 comments
[ 4.9 ms ] story [ 105 ms ] threadhttp://proto.actor/docs/dotnet/hello%20world
Akka.NET is production ready and has a huge community. has commercial support etc. There are however a fair number of design issues that I dislike personally which led me to start Proto.Actor
The .NET implementation of proto.actor is in alpha version right now. We are building and stabilizing the Go version first, which already is used in major production systems.
Serialization and the concept of the ActorRef. They both go hand in hand.
in Akka/NET, ActorRefs are contextual, they belong to a given ActorSystem.
This means that when they are serialized, the serializer needs to be able to know how to transfer and rebind this context on serialization and deserialization. To make it more complex, ActorRefs can be embedded in other messages. There are a few other primitives also that are contextual and serialized. This limits the options you have in terms of serializers. It also puts a massive tax on the framework in terms of maintenance.
ActorRefs are also "resolved" when deserialized, so the deserializing system has to figure out which actor is targeted. This lookup is slow due to parsing and scanning of actors and their children.
This makes network communication speed suffer _a lot_.
The configuration DSL HOCON is also tightly integrated with the entire infrastructure, making it hard to reason about what is going on and what parts of the config the current piece of code actually sees. Akka/NET also lacks good interception points, its hard to hook into actors and monitor them.
There are just too many moving parts for my own liking. I did blog about some of the problem areas in my POV about a year ago: https://rogerjohansson.blog/2016/03/13/random-things-learned...
So the issues are really both on a performance level and at a maintenance level.
The mindset in Akka.NET have been more "lets build everything ourselves from scratch to match the JVM" and in proto.actor "lets re-use proven tech and glue them together, with minimal code"
Actor-based approaches with hundreds of thousands of actors are really amazing, powerful and often simpler than equivalent channel based systems, conceptually. But they require special GC considerations to be made "efficient" of a really high level of mechanical sympathy to make sure you aren't creating pathologically bad situations for the garbage collector.
Take one or two features from Erlang into another language, and you have meh.
But what doesn't work is trying to slavishly copy the Erlang/Pony style without those considerations.
If that's what your domain space needs, use the BEAM and find a way to talk to it for the parts that you can't move to Erlang.
I say this as an big fan of Erlang, truth is, sometimes you just have to accept that your job is to write a python/Django backend without a line of JavaScript in order to serve as a line of business app, and if the load gets bigger, you just throw another server at it.
That said, erlang is great, elixir is awesome, and both are 100% worth every developers time to learn.
Can I be honest? As a language, Erlang kinda is a cube of frozen barf. It's not messy, but it's certainly not delicious. I do not "love it." I think most people sort of work around this; OTP is so good you put up with a kinda ugly syntactic substrate. Ideas good, array syntax bad, etc.
>powerful and often simpler than equivalent channel based systems, conceptually
Without true experience with any of agents/channels, what make it simpler? I read the GO and compare with Actors in F# or Erlang and for me it easier to understand channels. Mainly because are typed on the data.
I have not experience in this area.
But is the same if untyped. Because the internal schema is changed?
I suppose?
> Without true experience with any of agents/channels, what make it simpler?
If you are very clear about not retaining references to things you ship to another actor, Erlang is marvelously concurrent without any of the usual considerations for shared memory or functional programming we have to make. You can confidently manipulate and mutate variables in your actor scope because your actor is single threaded and OTP makes async-with-response calls trivial to mix in with this when you want function semantics.
Actor based systems give you a place to put sequential mutation code and a clear, clean, scalable way to define parallel operations. Channels really... well they give you this blunt force instrument to export data but you end up implementing actor-like management every time they're invoked if you're doing any sort of fan-out anyways.
I imagine this is the reason for build a custom-made VM, and not be a good fit inside Java/.NET?
It's great to see more actor runtimes on the rise. Also interesting to see the actor model implemented in Go, which follows more of a pi calculus design with its channels. With an actor design, the software architecture shifts towards the endpoints of the communication.
CAF targets the performance-sensitive C++ community, so sending a message in the same process only costs a single hardware-support CAS operation. We found this scales very well. No GC other than reference counting.
For those interested in learning more about actors in the C++ ecosystem, I recently gave a talk about CAF and some performance comparisons to other frameworks in various scenarios: http://matthias.vallentin.net/slides/caf-rise.pdf
If parent need a "done" channel, it passes it in and "actor" closes it on its way out.
Cleanup usually "signals" children and waits for them to be "done".
Sometimes "done" is just signalled by the goroutine returning and it is wrapped in parent somehow.
But every time I try to formalize it into some "framework" my abstraction fails.
e.g. see this CLI tool for managing proto.actor systems live: https://asciinema.org/a/a1b18jiv703e6eog1o2b7domi
You will be able to send messages to actors, connect to remote nodes etc.
There are also very fine-grained interception points allowing us to plug in things like zipkin.io w/o altering how the user writes their code.