News for hackers. Stuff that matters.
Re:Uber Go Style Guide by jrockway on Friday October 11, 2019 @05:16PM (#21228561)
Something that I don't see style guides addressing, but think they should, is how to cancel work when the caller no longer cares about the answer. (Consider an aborted RPC, or someone pressing the "stop" button in their browser.)

A lot of people will do things like:

  func (s *Server) HandleFoo(ctx context.Context, in *input) status {
     ch <- workFor(in)
     return status.Accepted
  }
This will block on the channel write even if the context becomes cancelled. Instead, you really need to be explicit about what you want to block:

  func (s *Server) HandleFoo(ctx context.Context, in *input) status {
     select {
     case ch <- workFor(in):
       return status.Accepted
     case <- ctx.Done():
       return status.Error("handle foo: wait for work to be accepted: %w", ctx.Err())
     }
  }
Now we don't wait for the work to be accepted if the caller decides it doesn't care about the job anymore.

My impression from digging around the open source world is that nobody except me cares about this. So maybe I'm just crazy. But I really like not leaking goroutines on long-running programs, making Control-C behave gracefully, etc.

Re:Uber Go Style Guide by Matthias247 on Friday October 11, 2019 @11:56PM (#21230551)
You are not crazy! However cancellation is an advanced topic. It requires some experience to get it right - and even to understand why it's required at all.

Most people will not see the need for cancellation - until they either need to implement a deadline timeout later on, or run into the problem that lots of outdated tasks take up their memory. Those things can come up years after the inital program had been written.

It's also not purely a Go thing. E.g. in Java thread interruptions are also barely handled correct. Or CancellationTokens in C#.

Re:Uber Go Style Guide by thezviad on Friday October 11, 2019 @05:30PM (#21228664)
the way to do is to pass context all the way through, until the other thing you are waiting on uses up no resources.

so in your example it should be done like this: ch <- workFor(ctx, in)

and the 'workFor' function should be the one that gets canceled with the context.

Deep down at the very end, you would have select statement with '<-ctx.Done()' and something else that doesn't need canceling (if it needs canceling it should have taken context too).

Of course the world isn't perfect and not everything takes context right now (even in standard library), so you do have to do some workarounds every once in a while.

Re:Uber Go Style Guide by jrockway on Friday October 11, 2019 @05:54PM (#21228834)
This is a very trivial example so doesn't dive into all the complexity. There is a lot of nuance here, like whether or not you really want to do an operation in the background in the first place. Background work does means that you lose the ability to apply backpressure to the calling system, and that will cause a lot of problems under load. Even if you do want to do the operation in the background, you still want to provide a (derived) context so that you can link the background work to the initial request (at the very least, for tracing purposes). That is omitted here for simplicity.

Where I was going with all of this was that the linked style guide mentions cases where people create buffered channels presumably because their channel writes start blocking. No buffer will make up for the case where the rate of work requested is higher than the rate at which work can be completed. What people really want in that case is the ability to get out of the channel write and return an error to the downstream system; you don't want to buffer, you want to cancel. There are many ways to accomplish the act of cancelling work, but you do have to watch your channel writes explicitly.

Re:Uber Go Style Guide by fishywang on Friday October 11, 2019 @09:20PM (#21229876)
It does not need to be the background. You can check context cancellation without any goroutines by:

    select {
    default:
    case <-ctx.Done():
        return ctx.Err()
    }
When there's no other channels to select with the context, just use that code block between lengthy operations to check context cancellation and return early.

(If you are curious about how many extra time is wasted, it's easy to write a benchmark test for that. Last time I checked it was ~20ns when you are going the default/not-cancelled route)

Re:Uber Go Style Guide by _caw on Saturday October 12, 2019 @01:41AM (#21230928)
You may also simply write the following, which appears more elegant than the select.

  if ctx.Err() != nil {
    //then the context has expired or been cancelled
  }
Re:Uber Go Style Guide by ncsurfus on Friday October 11, 2019 @05:37PM (#21228731)
Having gone from a lot of C# to some Go, I’m use to passing in a CancellationToken to all asynchronous methods. I’ve noticed that there’s a lot of Go libraries missing Context support too .
Re:Uber Go Style Guide by closeparen on Saturday October 12, 2019 @02:35PM (#21235203)
Ideally application code doesn’t have to manage this, it’s done in the RPC library before accepting work and while awaiting responses.