top | item 44546653

(no title)

ayuhito | 7 months ago

Go also suffers from this form of “subtle coloring”.

If you’re working with goroutines, you would always pass in a context parameter to handle cancellation. Many library functions also require context, which poisons the rest of your functions.

Technically, you don’t have to use context for a goroutine and could stub every dependency with context.Background, but that’s very discouraged.

discuss

order

arp242|7 months ago

Having all async happen completely transparently is not really logically possible. asynchronous logic is frequently fundamentally different from synchronous logic, and you need to do something different one way or the other. I don't think that's really the same as "function colouring".

And context is used for more than just goroutines. Even a completely synchronous function can (and often does) take a context, and the cancellation is often useful there too.

tidwall|7 months ago

Context is not required in Go and I personally encourage you to avoid it. There is no shame in blazing a different path.

schrodinger|7 months ago

Why do you encourage avoiding it? Afaik it's the only way to early-abort an operation since Goroutines operate in a cooperative, not preemptive, paradigm. To be very clear, I'm asking this completely in good faith looking to learn something new!

atombender|7 months ago

It's not required, but eschewing it ends up going against the grain, since so much of the ecosystem is written to use contexts, including the standard library.

For example, say you instead of contexts, you use channels for cancellation. You can have a goroutine like this:

    go func() {
      for {
        select {
        case <-stop:
          return
        case <-time.After(1*time.Second):
          resp := fetchURL(url)
          processResult(resp.Body)  // Simplified, of course
        }
      }
    }()
If you want to be able to shut this goroutine down gracefully, you're going to have an issue where http.Get() may stall for a long time, preventing the goroutine from quitting.

Likewise, processResult() may be doing stuff that cannot be aborted just by closing the stop channel. You could pass the stop channel to it, but now you're just reinventing contexts.

Of course, you can choose to only use contexts where you're forced to, and invent wrappers around standard library stuff (e.g. the HTTP client), but at that point you're going pretty far to avoid them.

I do think the context is problematic. For the purposes of cancellation, it's invasive and litters the call graph with parameters and variables. Goroutines really ought to have an implicit context inherited from its parent, since everything is using it anyway.

Contexts are wildly abused for passing data around, leading to bloated contexts and situations where you can't follow the chain of data-passing without carefully reviewing the entire call graph. I always recommend not being extremely discriminating about where to pass values in context. A core principle is that it has to be something that is so pervasive that it would be egregious to pass around explicitly, such as loggers and application-wide feature flags.

nu11ptr|7 months ago

What would you use in its place? I've never had an issue with it. I use it for 1) early termination 2) carrying custom request metadata.

I don't really think it is fully the coloring problem because you can easily call non-context functions from context functions (but not other way around, so one way coloring issue), but you need to be aware the cancellation chain of course stops then.

osigurdson|7 months ago

I think the main point is in something like Go, the approach is non-viral. If you are 99 levels deep in synchronous code and need to call something with context, well, you can just create one. With C#, if you need to refactor all 99 levels above (or use bad practices which is of course what everyone does).

Also, in general cancellation is something that you want to optionally have with any asynchronous function so I don't think there really exists an ideal approach that doesn't include it. In my opinion the approach taken by Zig looks pretty good.

phplovesong|7 months ago

Like you said you dont NEED context. Its just something thats available if you need it. I still think Go/Erlang has one of the best concurrency stories out there.

zer00eyz|7 months ago

> If you’re working with goroutines, you would always pass in a context parameter to handle cancellation.

The utility of context could be called a subtle coloring. But you do NOT need context at all. If your dealing with data+state (around queue and bus processing) its easy to throw things into a goroutine and let the chips fall where they will.

> which poisons the rest of your functions. You are free to use context dependent functions without a real context: https://pkg.go.dev/context#TODO

oefrha|7 months ago

The thing about context is it can be a lot more than a cancellation mechanism. You can attach anything to it—metadata, database client, logger, whatever. Even Io and Allocator if you want to. Signatures are future-proof as long as you take a context for everything.

At the end of the day you have to pass something for cooperative multitasking.

Of course it’s also trivial to work around if you don’t like the pattern, “very discouraged” or not.