Hacker News new | past | comments | ask | show | jobs | submit login
Suture – Supervisor Trees for Go (jerf.org)
96 points by dataminer on March 28, 2016 | hide | past | favorite | 13 comments



A report, after using this library for more than a year and a half: First, yes, I still use it in every program that is not a pure web server or pure CLI. My Go programs definitely resemble my Erlang programs in that they are ultimately trees of supervisors. Depth is about the same; I rarely go more than three layers deep and the trees are quite breadth-oriented rather than depth.

Second, I have found a lot of utility comes from having a standardized "this is my set of services" object that can be manipulated. A lot of my Go programs have converged on "a lot of libraries providing a lot of services, and a rather grotty-looking ~100-200 line 'main.go' that does all the nasty ops-type work of propagating configuration to them and plumbing them all together". Most of those main.go's have a supervisor created near the top, various bits of library functionality added to the supervisor as the file goes along, and the last line is supervisor.Serve().

In terms of error handling, while I have seen it keep production services up in a reasonable manner, Go code tends not to crash much, if you mind your errors properly. The orchestration may actually be the nicer thing. I've gotten a few issues in the github project and refined things a bit over the past year; one of the use cases that came up is that shutting down the services with one ".Stop()" call has been useful to people.

(We also got an interesting example of when penetrating an abstraction can come in handy; supervisors take a couple of functions to allow you to log some things. When a supervisor child is added to a supervisor parent, the log functions are copied from parent to child by penetrating the Service with a type assertion. This makes it easier to compose supervisors because the child supervisors no longer have to guess at what logging you want.)

All in all, while I will re-emphasize strongly that it is not a straight-up replacement for all bits of Erlang functionality as I explain at some length in the post, I have found it useful enough to be worth writing and using myself. Whether it passes the bar for "worth importing as a dependency", well, that's your call.

(Also, I'm guessing this came up again for the explanation of why imperative languages generally don't have thread-killing capabilities?)


That's sorta how I was approaching my Go services when I was writing Go: https://github.com/ProTip/cw-engine . I would write the main functionality as an "engine", something that could be embedded(hosted) in another Go app and then use it in my own main.go executable.

This is really cool BTW, great work! I was following it more when it was first announced but unfortunately I haven't been using Go for work these days :| Would be fun though to take some old stuff and rejig it with Suture. You're right though, the Go code doesn't crash much.


Loved the article! I'm not familiar with Erlang, but I was able to follow along and finally understood why OTP is usually distributed with Erlang.

If an Erlang process crashes when processing a message, and it is restarted, is the message replayed automatically by the runtime, or provide some kind of mechanism to recover the message as well?


Erlang messaging's contract is 0-or-1 delivery. Once a process has received a message, it is in the recipients mailbox and no longer the runtime's problem. Resulting failures can theoretically cascade across a couple of processes tightly bound to that message's result, but handling that is Erlang's bread-and-butter, so generally, a couple of processes will drop dead, then be restarted. Some request will get lost but the system will keep going.

You can also do things like send a message across a node boundary to a node no longer connected, which looks a lot like sending a message to a process that crashes it. In the general case, you don't get an "error" message when that happens or anything, you just get timeouts as if the remote node received it but never acknowledged it. It's "just another failure", just another day at the office, nothing unusual here.


Suture looks very good, and as you said earlier it doesn't try to do a 1-1 translation of Erlang's supervisor. However you touched on live (re)load and multi-machine clustering in Erlang with no word about it with suture; I guess it's not supposed to do it ?



Can you share more of the reasons for porting from Erlang to Go?



How do languages like C deal with interrupts? Is it possible to deal with asynchronous exceptions if your handlers are reentrant?

I suppose this would still have issues dealing with unfinished work under a mutex.


Would it be possible / desirable to use Suture to implement something like Supervisord [1] in Go? This is something Docker image maintainers might want, because it would eliminate the dependency on Python.

1: http://supervisord.org/


Nice to see this again! I caught one of the earlier postings and lost track of it over time.

Are there any more examples available? Folk using it in production?


From 2014. Started reading and recognized it.


In imperative languages you can safely deliver "asynchronous exceptions" only if you write your code asynchronously, i.e. in a form of asynchronous events. Event loops usually deliver OS signals into event-driven programs in exactly this way. And it's always safe, because none of the event handlers can be interrupted or run in parallel, the whole program is designed with that assumption.

This is how asynchronous processes might look like:

   func procA (cb func(error)) (func(error)){
      var enter, something, leave func(error)
      var shared int
      enter = func (error err){
         ...
         shared ++
         ...
         waitForSomething (something) 
      }
      something = func (error err){
         if err != nil { 
            post (leave)
            return
         }
         ...
      }
      ...
      leave = func (error err){
         ...
         post (cb)
      }
      return enter
   }
It is also easy to preserve same guaranties and schedule such processes to multiple cores. Although to do that in Go you would need to explicitly pass a struct with all of the information about each process, its resources and its environments.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: