10 ms·
Ergo: Erlang-inspired event driven actor framework in Go
- throwaway81523 2y agoI remember wanting to do a Go port of OTP and call it GoTP. It's hard for supervision trees to work properly in Go, unfortunately.
- Xeoncross 2y ago> Zero dependencies This is something seldom attempted, but I congratulate you. Go is one of a few languages that really is batteries-included. Just about anything you could need is included in the stdlib.
- seanw444 2y agoOne of the reasons I prefer it over something like Rust for most projects. I don't have to waste time figuring out what third-party library is the defacto standard that I should be using.
- iknowstuff 2y agoIt’s a tradeoff. Do you prefer to be stuck with bad built-in file and time APIs, or a robust ecosystem of external crates? https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-...
- hu3 2y agoSo your argument is that an ecosystem of external crates which are created and maintained mostly by individual contributors is on average better than the standard library backed and dogfooded by trillion dollar company and used by other giants of the industry? Can't say I agree. Not to mention nothing prevents anyone from using or writing their own library only for the parts that need specialization. You're free to do that and many have. And standard libraries can be versioned and deprecated too.
- everforward 2y agoI actually wouldn’t be surprised, if only because the standard library is so much harder to make backwards-incompatible changes to. I would generally expect that the average quality of the third party libs is lower, but the top 1% is probably better than stdlib. Eg I don’t find the stdlib logging library particularly great; not bad, but not impressive. Ditto for the stdlib errors package before they added error wrapping
- iknowstuff 2y agoUh, which ones would you like to compare? C++ std regex vs rust’s regex crate?
- mijamo 2y ago"robust ecosystem" is a rather optimistic view of the rust situation... I would have said "a bunch of 0.x libraries that do 80% of the work and let you figure you the hard 20% while being slightly incompatible with each other, and that will be painful to glue together because of the rules for traits"
- pjmlp 2y agoI rather have something that works everywhere there is a full implementation of the platform, instead of whatever third parties decided to support.
- kbolino 2y agoThe file API is mostly fine. Opening with that is the wrong approach to me; the comparison with Rust doesn't really illustrate any serious issues. All of the complaints could be addressed with better documentation. The time API, on the other hand, is that bad, and worse. Besides the bizarre monotonic time implementation, there's also the bloated size of the time.Time struct, and the lack of actual "dates" (YYYY-MM-DD) and "times" (HH:MM:SS) that you can do arithmetic on. You can't really roll your own robustly either because time.Time isn't a great foundation to build on and //go:linkname is getting locked down. Thankfully, build constraints are much better now, since they just use regular boolean operators, e.g.: //go:build windows && (i386 || amd64) I agree that there shouldn't be any _buildtag.go magic, and I think they ought to remove that "feature" entirely in a future version of Go (which can be done so that it doesn't affect older or unversioned code). It seems they added a "unix" build tag too. Also, one of my personal gripes is that the standard library exposes no guaranteed way to access the system CSPRNG. Any code can replace (crypto/rand).Reader with another implementation and compromise cryptographic operations. It's a trivial supply-chain attack vector, and even in non-malicious scenarios, it can be done mistakenly and break things all over the place in a subtle but dangerous way. The language developers have so far refused to fix it too. Then there's log/slog with no TRACE or FATAL levels built-in; yeah, you can roll your own levels, but why should you have to?
- pjmlp 2y agoAnyone with enough experience in C derived languages large scale preprocessor spaghetti, welcomes _buildtag.extension alternative. This is actually one of the few things I fully agree with Go designers.
- kbolino 2y agoIs the file foo_bar.go (no build tag line) compiled or not? If your Go version doesn't know of such a build tag as "bar", then foo_bar.go is unconditionally compiled. If Go in a later version adds "bar" as a known build tag, then foo_bar.go becomes conditionally compiled. Better hope you know this is how things work (reality: lots of Go devs don't). Build tag lines don't have this problem. They always specify compilation conditions, even if the build tag is not already known to the compiler. They also apply to the whole file, the same as _tag in the name; there's no preprocessor spaghetti possible.
- Thaxll 2y agoRust has multiple http server and async framework which creates a lot of confusion.
- metadat 2y agoGreat example, and more of a horror show than I anticipated. It would have been interesting if the author had a suggestion on what the Golang team should've / could've done instead, beyond correctly communicating the nature of the breaking change in the Go 1.9 release notes.
- _mlbt 2y agoInterestingly, Erlang is very batteries included as well. Probably even more so than Go in most cases.
- worldsayshi 2y agoDoes Elixir inherit those batteries or is the ecosystem partially disconnected from Erlang?
- brightball 2y agoElixir inherits it. You can call anything in Erlang directly.
- throwawaymaths 2y agoNot strictly true. Some stuff in the Erlang distribution must be included in extra_applications before you can call them.
- seivan 2y ago[dead]
- pjmlp 2y agoStill doesn't have half of batteries that Python, Java and .NET include.
- philosopher1234 2y agoNo doubt that is true, but are there particular batteries you’re thinking of?
- pjmlp 2y agoGUI, dependency injection, huge collection libraries, dynamic runtime Interoperability, distributed objects, compiler plugins, custom runtime schedulers,... for example. Some of the above will never be in Go due to how the community and language designers are philosophically against them.
- limit499karma 2y ago> dependency injection It's been a while since I played with the furry thing but is that even possible in Golang?
- pjmlp 2y agoI was speaking about Python, Java and .NET standard libraries. In Go you can naturally do it, by using the manual constructor approach, however there is no magic auto wiring like you can do with attributes and compiler plugins, plus standard libraries infrastructure for locating services, from those three ecosystems above.
- limit499karma 2y agoWell, in my head DI at this point requires the magic bits. Otherwise it is (as you say) just constructor args.
- pjmlp 2y ago
- deleted 2y ago[deleted]
- Xeoncross 2y agoNever having worked with Erlang, my mind is telling me this might be an alternative for https://github.com/temporalio https://github.com/temporalio (database-backed functions) or AWS Step functions.
- seneca 2y agoThey're not the same, exactly, but there are similarities. The actor framework isn't inherently durable workflows, for example. It does similarly distribute work though.
- brightball 2y agoSupervisors give you the durability though.
- jerf 2y agoA different kind of durability than what people mean by "durable workflows", though. Durable workflows require durable storage of their current state; supervisors give you durable computation services. Supervisors don't even guarantee "durable computation"; if a process crashes halfway through processing a request, it's not like the request will be automatically retried or something. (That isn't even a good idea, there's a very good chance the reason why that request crashed one process will crash others.)
- brightball 2y agoYou can actually set it up that way though. You set a process to keep the state and a process to do the work. If the process doing the work crashes, the state is preserved. It’s a deliberate decision but very easy with the BEAM.
- jerf 2y agoThat's still not durable; you've set up a single point of failure in whatever is hosting the state process. Durable storage is a fundamentally different problem which you can't solve just by waving some Erlang processes at the problem. You could write a durable storage system in Erlang, although there's not a hugely compelling reason to do so when you can also use any existing one, and writing this sort of system is extremely challenging. Heck, just (fully) understanding Raft or Paxos is extremely challenging, let alone implementing it. Some classes of problems are best solved by a limited number of best-of-breed solutions that all of the other language ecosystems just use, like, durable storage and databases in general, or ffmpeg, or browsers. (I can also tell you from personal experience Mnesia isn't a "durable storage" solution. Rather the opposite, honestly.)
- glutamate 2y agoLove the idea, but i am having a hard time finding out what the code looks like. Where can i see the code for spawn, receive and send? > ... a command-line utility designed to simplify the process of generating boilerplate code for your project based on the Ergo Framework Why is there any boiler plate code at all? Why isn't hello world just a five line programme that spawns and sends hello world somewhere 5 times?
- whalesalad 2y agoI was looking for the same thing. A project like this really needs an `examples/` directory with a few projects to sink your teeth into. I've been thinking for years that if a project existed like this for Python it would take over the world. Golang is close, I guess.
- nvarsj 2y agoIt's right there. https://github.com/ergo-services/examples https://github.com/ergo-services/examples It looks like a close copy of Erlang APIs, albeit with the usual golang language limitations and corresponding boilerplate and some additional stuff. Most interesting to me is it has integration with actual Erlang processes. That could fill a nice gap as Erlang lacks in some areas like media processing - so you could use this to handle those kind of CPU bound / native tasks. func (a *actorA) HandleMessage(from gen.PID, message any) error { switch message.(type) { case doCallLocal: local := gen.Atom("b") a.Log().Info("making request to local process %s", local) if result, err := a.Call(local, MyRequest{MyString: "abc"}); err == nil { a.Log().Info("received result from local process %s: %#v", local, result) } else { a.Log().Error("call local process failed: %s", err) } a.SendAfter(a.PID(), doCallRemote{}, time.Second) return nil
- whalesalad 2y agoI can't tell which syntax is worse: erlang or golang.
- Fire-Dragon-DoL 2y ago
- that_guy_iain 2y agoI love the name. It's just so good.
- Fire-Dragon-DoL 2y agoHow does the supervision tree look like? My major problem with go are goroutines bringing down tge whole software
- helsinki 2y agoHow?
- Fire-Dragon-DoL 2y agoThe syntax for declaring it, sorry
- mh- 2y agoI'm not the person you're replying to, but this left me with more questions than answers, haha. Can you expand on what you mean?
- Fire-Dragon-DoL 2y agoWhat's the equivalent of this: https://hexdocs.pm/elixir/supervisor-and-application.html https://hexdocs.pm/elixir/supervisor-and-application.html defmodule KV.Supervisor do use Supervisor def start_link(opts) do Supervisor.start_link(__MODULE__, :ok, opts) end @impl true def init(:ok) do children = [ KV.Registry ] Supervisor.init(children, strategy: :one_for_one) end end In ergo
- yellowapple 2y agoAre the actors/processes preemptively scheduled? I've seen many projects like this over the years, and that's the usual missing feature.
- Fire-Dragon-DoL 2y agoGoroutines are preemptive scheduled, so the answer is yes
- __MatrixMan__ 2y agoI like that this is a thing, I'm trying to decide how excited I should be about it... How equivalent are BEAM processes and goroutines?
- kbolino 2y agoMost significantly: goroutines are not addressable. This means that, from application code, you cannot send messages directly to a goroutine [1], you cannot link two goroutines, you cannot terminate a goroutine from another goroutine, you cannot directly watch a goroutine's exit [2], and there's nothing akin to the process dictionary (there are no "goroutine-local variables"). [1]: you usually use channels instead [2]: you usually use sync.WaitGroup (or similar, e.g. errgroup.Group) instead
- pancsta 2y ago> you cannot send messages directly to a goroutine Goroutines communicate through channels, all you need is a queue (eg buffered chan). > you cannot terminate a goroutine from another goroutine Termination is propagated via context cancellation. go-A cancels ctx, go-B waits with `select`, reads from `<-ctx.Done()` and does a `return`, or checks it after each blocking call. > there are no "goroutine-local variables" Not sure if I got this one, but every var in a function's scope, which has been `go`-routined, would qualify. I'm currently working on a lib/framework somehow related to Ergo, but taking a more "generic" approach of a state machine[0]. It may solve some of the mentioned issues with Go, like addressing and queues for communication. You seem to be very attached to an idea of using the same goroutine for a long time, whereas it's usually more dynamic and only schedulers are long lived `go`-s. [0]: https://github.com/pancsta/asyncmachine-go https://github.com/pancsta/asyncmachine-go
- kbolino 2y agoPlease note that the context of these points is in comparison to BEAM processes, as used by Erlang and Elixir. It is a comparative analysis of goroutines, not an holistic critique of them. I appreciate the mention of context cancelation, that is a good example of how to terminate a goroutine, provided that a) the goroutine has a context to cancel and b) the goroutine does not spend too much time in operations that can't be canceled (e.g., file system calls, time.Sleep, blocking select without case <-ctx.Done(), etc.). This is still cooperative multitasking though, while BEAM processes are fully preemptive. A "goroutine-local variable" would be a variable accessible by other goroutines which is still scoped to a particular goroutine. Ordinary local variables are only accessible by the goroutine in whose stack they belong. Something like the process dictionary can be constructed in Go using a synchronized map with atomic values, but it certainly isn't a built-in part of the runtime like it is in BEAM. I generally don't regard goroutines with much attachment. There are, however, some situations in which goroutines must be long-lived: the main goroutine, the "scheduler" goroutines you mentioned, and in general any goroutine performing a supervisory role over other goroutines. Monitoring these important goroutines is more complicated in Go than BEAM languages, but certainly isn't impossible. The more problematic aspect of goroutines vs. BEAM processes is that, due to the lack of application-accessible preemption, when a critical-path goroutine does get stuck, the only real solution most of the time is to kill the entire program. This is rarer than it was in Go's early days, at least.
- debo_ 2y agoIt has a great name, ergo I support it. /joke
- samuell 2y agoThis is super exciting! I've written before about how I think the more FBP-style concurrency of Go and the message passing one in Erlang complement each other as much as streaming DNA processing inside the cell and fire and forget cell to cell signaling between cells do in biology: https://livesys.se/posts/flowbased-vs-erlang-message-passing/ https://livesys.se/posts/flowbased-vs-erlang-message-passing... The FBP/CSP-style in Go being more suited for high performance streaming operations inside apps and compute nodes (or cells), while the message passing mechanism shines over more unreliable channels such as over the network (or between cells). Ergo seems like it might allow both of these mechanisms to be implemented in the same language, which should be a big deal.
- nahuel0x 2y agoThree big differences in comparison with Erlang: 1- Cannot externally kill a process (yes, ergo process have a Kill method but the process will be in a "zombie" state until the current message handlers returns... maybe stuck forever) 2- No hot code reloading. 3- No per-process GC.
- didip 2y ago(Not affiliated with Ergo) I have been watching Ergo repo for a while now and you wrote my observation concisely. A while back, I tried to solve no.2 on your list by having a dynamically expanding and shrinking go-routines. POC here: https://github.com/didip/laborunion https://github.com/didip/laborunion. It is meant to be used in-conjunction with config library & application object that's update-able over the wire. This idea is good enough to hot-reload a small IoT/metrics agent, but I never got around to truly solve the problem completely.
- mportela 2y agoGreat project name, though
- justinclift 2y agoThat could be another way to have AWS not poach a popular Open Source project. Name it something extremely pro-unionisation, such that every time they write the name they'd be reminding their developers that unions exist. :)
- throwaway894345 2y agoI've never written any Erlang before--why do I care about per-process GC?
- com 2y agoMuch lighter impact on system performance that world-GC. Simpler algorithms as well, so lower risk that you’ll ever get performance regressions - or worse.
- mervz 2y agoJust use Elixir... it's a better language overall.
- mr90210 2y agoPeople didn’t get the gist of your comment. Here is another way of looking at it: One could write mobile apps and mobile games in Go, but should you really?
- cynicalsecurity 2y agoGo is not the correct language for this. This must be written in a language that support manually freeing variables from the memory. Go is a garbage-collected programming language. Go is also not supposed to be used in a complicated way. Its use cases must remain simple.
- mr90210 2y agoI have never written anything in Erlang, but if my system requires some of Erlang’s built-in features, I am picking Erlang or Elixir.
- giancarlostoro 2y agoThis is strange, I don't know when it changed, but this project used to be compatible with Erlang directly...[0] Used to support the OTP. I guess their needs changed? You literally could write Go code that would talk to Erlang itself, I'm not sure why this has changed or where that functionality has gone but the page aside from a repository tag no longer mentions OTP. I was looking at this project for that very goal a month or two back. [0]: https://news.ycombinator.com/item?id=34559409 https://news.ycombinator.com/item?id=34559409
- AlphaWeaver 2y agoIt's still present, they've moved it to a separate repository that's now commercially licensed.
- Dowwie 2y agocan I remote into a Ergo runtime and inspect state?
- rad_gruchalski 2y agoMy go to actor framework for golang has always been https://github.com/asynkron/protoactor-go https://github.com/asynkron/protoactor-go. It seems that both protoactor and ergo are heavily influenced by Erlang. Why would one select ergo over protoactor?
- throwawaymaths 2y agoCan you link processes? goroutines? in ergo?
- helsinki 2y agoWhat kind of practical, real-word problems can I solve with this, as a backend engineer at a hedge fund? Im struggling to understand why I even need actor frameworks when my processes are already supervised, pretty much never crash, and all communicate over gRPC and Kafka? Not trying to be snarky, just don’t understand how this paradigm could assist me. For more context, most of my work is around real-time streaming / monitoring of trades and trading metrics.
- akvadrako 2y agoYou are probably thinking too course-grained. Erlang-style actors are closer to functions than processes. For example you could parallelize a for loop by creating a goroutine for each item in a list. Or separate actors for logging, session tracking, connection pools, etc. Doing that with processes and Kafka would be massive overhead.
- giovannibonetti 2y agoThose looking for a type-safe actor model implementation may find the Gleam language [1] that runs on the actual BEAM VM and its OTP library [2] more interesting. It's type system is much closer to Rust, with native algebraic data types. [1] https://gleam.run/ https://gleam.run/ [2] https://hexdocs.pm/gleam_otp/ https://hexdocs.pm/gleam_otp/
- widdershins 2y agoI'm now sort of fascinated to know if it would be possible to compile Gleam to Go, and use this library to emulate the Erlang process stuff...
- stackghost 2y agoNot to be confused with ergo, the ircd[0] nor with ergo, the cryptocurrency[1] [0] https://github.com/ergochat/ergo https://github.com/ergochat/ergo [1] https://github.com/ergoplatform https://github.com/ergoplatform
- binary132 2y agoEvery time I see people trying to build concurrency / process lifecycle frameworks on top of Go, I think they missed the point of Go. YAGNI. If you do really need it, just use erlang or whatever, where it’s world-class and battle hardened.
- lakomen 2y agoA more hands on demonstration would be welcome. This generic demo with its naming isn't helping. What is the connection between an actor and a database for instance? Why do I need actors?