Using context package for goroutines cancellation is quite verbose

133 views
Skip to first unread message

Yegor Roganov

unread,
Jun 8, 2020, 12:59:57 PM6/8/20
to golang-nuts
Hello,

My team is developing a cloud-based data processing application, and a large bulk of the application's job is doing network calls to other services.
Go's goroutines and channel-based communication are an excellent fit, but in order for the application to be able to properly shut down,
simple construct of sending to a channel `myChan <- value` needs to actually be something like:

```
select {
case myChan <- value:
case <-ctx.Done():
return ctx.Err()
}
```

As you see, a simple one line construct needs to be replaced with a select on context.Done EVERYWHERE.
My two gripes with this situation are:

1) It's more code and "clutter" which makes code less clear
2) It's easy to forget to do a select on context.Done, and then an application is unable to shut down.

Am I doing something wrong? Does someone relate with me?

Ian Lance Taylor

unread,
Jun 8, 2020, 3:46:05 PM6/8/20
to Yegor Roganov, golang-nuts
You are not doing anything wrong, and, you're right: it's more code.

In general, Go prefers code to be explicit rather than implicit, so I
don't really agree that this makes the code less clear. I think that
this code is clear to anybody who knows Go, and with more experience
it becomes fairly idiomatic.

But you're absolutely right that it's easy to forget to do a select.

Personally I think that this is an area where generics can help. For
example, from the design draft published last year, see
https://go.googlesource.com/proposal/+/refs/heads/master/design/go2draft-contracts.md#channels
. I think the right step here is going to be see how generics can
help with these common patterns before trying other approaches. I
acknowledge that that is a slow process.

Ian

Yegor Roganov

unread,
Jun 9, 2020, 8:04:21 AM6/9/20
to golang-nuts
Thanks Ian, it's nice to know that we're using Go correctly.

I agree that more code doesn't really matter, and I think I'd be completely fine with this situation had there been a vet check that ensured that no selects are forgotten.
Let's see if generics change something in this area.

Jonathan Amsterdam

unread,
Jun 10, 2020, 10:15:33 AM6/10/20
to golang-nuts
Typically, a network communication system, like gRPC for example, will accept contexts for network calls and turn context done-ness into an error. The typical signature of an RPC-backed function is

    func(context.Context, req *Request) (*Response, error)

If the context times out or is cancelled, a good RPC library will return from this call immediately with a non-nil error. So context done-ness checking is just part of ordinary error checking.

I'm not sure how you're mapping the RPCs to channels. If you're doing

    res, err := RPC(ctx, req)
    if err != nil { return ... }
    ch <- res

then you indeed have a problem: you'll need another check whenever you receive from the channel. But if you're doing

    res, err := RPC(ctx, req)
    ch <- rpcResult{res, err}

then receivers don't have to check ctx.Done, but of course they do have to check the error:

    result <- ch
    if result.err != nil { ...}
    // use result.res
 
Channels are a bit awkward either way, so for the Google Cloud client libraries we use an iterator pattern (https://github.com/googleapis/google-cloud-go/wiki/Iterator-Guidelines).
Reply all
Reply to author
Forward
0 new messages