Idiomatic way to do cancelations

In golang we have the package context that can be passed along a request lifecycle to do cancelations. In elixir, what is the idiomatic way to do this?

1 Like

If you model your actions as processes, aka the Elixir way, you can link those processes so that if one exits abnormally they all will.

As for timeouts, call/cast api’s (msg passing between processes) supports timeouts which will return with failure after that timeout.

is that what you are after?

1 Like

After a quick scan of Go Concurrency Patterns: Context I figured:

  • HTTP requests are typically handled in Elixir with Plug where the request/response is represented by Plug.Conn.
  • Processing of the request is effectively “canceled” with Plug.halt/1

Though in the grander scheme of things that may just be a starting point … as already mentioned exits are signals in Erlang/Elixir - if a process has to clean up after itself, it needs to trap exits (Process.flag(:trap_exit, true)) and if more than one process is involved there is typically a supervisor process.

So there is a whole range of options to serve scenarios of varying complexity.

1 Like

Thanks! I think i got the way it need to be done, going to study more otp.

If a client disconnects when the request is being handled, is the plug pipeline halted?

Probably not, but the cowboy process, processing this connections, dies.

Wow, so it works the same as in Go, without having to do anything explicitly?

E.g. if the cowboy process exits, then if it was calling out to the database, is the database call automatically cancelled? And everything is cleaned up?

I don’t think it’s automatic like this, but I am not particularly familiar with Ecto (a popular db wrapper in elixir). They might be trapping exits also, or they might be not. If they do, they might cancel the database call on the exit signal from the cowboy process that served the connection.

Doesn’t seem like they do [0].

But I don’t want to misinform you … Maybe someone from the ecto team can provide more insight on how it deals with “dangling” database calls.

There actually was a question about it, but I don’t think anyone came up with a solution at the time [1].

[0] https://github.com/elixir-ecto/ecto/search?utf8=✓&q=trap_exit&type=
[1] Is it possible to stop query execution on db server when process that start to run that query killed?

Yes. If whatever checked out the connection crashes, the connection is cleaned up and put back in the pool.

@idi527 this is done by db_connection and the database adapters, that’s why you won’t find it in Ecto’s source. :slight_smile:

1 Like

It’s not same as context in Go though, it might be actually useful to have a similar convention.

What does Go’s context solve that linking processes does not?

Desire to cancel a request is an orthogonal concern to something failing.

In terms of motivation you are correct and maybe I’m am misunderstanding what you are trying to say but exit signals aren’t necessarily limited to failures.

It’s simply a way to terminate a whole set of aggregated processes working towards some kind of common goal that has become unachievable or undesirable. So one of those processes receiving a “cancel” message and then proceeding to terminate all linked processes equates to the goal becoming undesirable - it isn’t considered a failure.

About the context package in Go and the similarities between it and Plug Conn, I wonder if it is a good or bad solution as described in this post:

https://faiface.github.io/post/context-should-go-away-go2/

What do you think?

From what I read they are entirely unrelated and are different concepts.

Plug.Conn in Elixir is just the network socket and some associated data about the connection. It is not needed to cancel it, to do work, or anything else of the sort.

context in Go seems like the network state at the time, it would be more akin to an Actor Process in Elixir, not a plug connection. To ‘cancel’ a context you have to tell it to do so. To ‘cancel’ an Elixir Process you just, well, exit it or throw or whatever you want. The Process ‘is’ the context in BEAM-land.

EDIT1: Also thanks for introducing me to ‘yet another’ design quirk that I really dislike about Go. Further convincing me to not use the language as it just seems poorly designed every time I look at it. ^.^;

EDIT2: Even more reading shows that the goroutine ‘tree’ cancelable (in the form of context) needs to be threaded-throughout, this is like the Supervision tree of OTP, which again is not like Plug.Conn in any form, plus it ‘just exists’ in OTP by virtue of process links, not be some magical context that needs to be passed everywhere. Notice that this is the ‘default OTP model’, unlike in Go where you have to explicitly and remember to pass contexts everywhere. That sounds horrid…

EDIT3: And reading over https://blog.golang.org/context and https://golang.org/pkg/context/ just further reinforces that it is a very poor form of OTP. They are trying to emulate timeouts (built in to the BEAM), supervising subtrees (OTP Supervisors), marking things as canceled (which still run to completion unless explicitly checked, where in OTP everything that depends on something that was canceled just dies outright and immediately due process links), and more. This whole Context thing in Go seems like an absolutely horrid design… but that seems to go with what I’ve seen of ‘Go the Language’, so eh…

2 Likes

Some things that seems very similar to me is the use of Plug.Conn as a bag to carry values accross the stack through use of the assign function, which seems a very pragmatic decision, but sometimes this could grow as a kind of global variable with a lot of thinks in there.

Only values that relate to the state of the connection. The Plug.Conn should never even really escape your Controllers, if it does you are doing “Something Wrong”. ^.^;

Which is quite the opposite of Go’s context, which is “Infect The Everything!”. ^.^

1 Like

Sure, Thank you for the explanation.