46 comments

[ 4.9 ms ] story [ 105 ms ] thread
I am interested and I will certainly read the Go version of the documentation, but the .NET of the docs are showing Go code instead.

http://proto.actor/docs/dotnet/hello%20world

Yeah I was going to mention this as well. Wanted to look at C# code and kept finding the Go code in the Docs.
And im getting bunch of 404s for .net docs. Which means its not really a serious project
Tried to click on the "Books" link and got a 404. I get the feeling they really aren't ready for visitors yet.
Not sure why it should be trusted more than Akka or Akka.Net. Akka is much much more than just "actors".
I think part of the appeal is that it's built on gRPC, so this could probably be expanded to any of the supported languages on that platform.
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?
Sure.

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"

(comment deleted)
.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.
The author of this wrote Akka.net first, then wrote GAM (the original name of this project, supporting only go), then implemented the net counterpart
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.

I think the Actor paradigm is valuable as a modeling tool and is widely dismissed these days by arguments I don't think are actually very valid.

But what doesn't work is trying to slavishly copy the Erlang/Pony style without those considerations.

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.

It's not hard to justify; that's laughable. It's simple redundancy!
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.

Welllllllll...

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.

Channels will be a better fit inside .NET?

>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.
But why are types a problem?
Because typing errors and infinite timeout is really hard..
I can't understand this. Can you give a more concrete example?
How do you prove something that is undecidable ?
Look, I honestly don't understand what you are talking about. Why suddenly we are talking about undecidable?

I have not experience in this area.

If something have an infinite timeout, how can you know it will be the correct type that come back, because you can not know what comes back.
Ok, but is the same problem if is untyped, or you mean that while program A is waiting for B to return [String], program B is updated to return [Int]?

But is the same if untyped. Because the internal schema is changed?

what is the usage of your type if you can not check them through the compiler ?
> Channels will be a better fit inside .NET?

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.

Ok, this make it clear a true advantage, having the mutations (and I assume, stack and related) isolated is compelling.

I imagine this is the reason for build a custom-made VM, and not be a good fit inside Java/.NET?

(Disclaimer: CAF co-maintainer here.)

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.
Is signals your "done" channel?
I was just trying to convey an example. I usually "signal" that the parent wants to stop this "actor" for some reason (e.g graceful shutdown).

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.

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
I'm getting a "Page not Found" from this link.
One of the major things will be the tooling around it. As we work with a well defined schema. we can create tools that work for all implementations.

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.