0

Note: I searched this topic with google, and read nearly everything I can find, but still cannot get a proper/reasonable/production ready answer.

Basically all answers are similar, just like this one: how to stop a groutine, all in same pattern, no exception: the real work is fmt.Println(1) to print something, or simply be // Do other stuff,

But if keep the real work at for select default case branch, then it will be executed multiple times, for printing something it is fine, but clearly it is not ready for real work.

The only valid approach I can imagine is put the real work on a case branch then send only one signal to that case to notify it to start, like here: playground, but just feel wried, also with this approach, does it create some issues potentially?

Added code fragments to show exactly what i am trying to achieve: https://play.golang.org/p/7xjjiW7XdoQ, i want to achieve when client close connection, then immediately terminate my handler, release the resources, and quit unconditionally.

lnshi
  • 2,310
  • 3
  • 19
  • 42
  • 1
    If you don't want to execute something multiple times, then don't do that. If you want to execute something once per value received over a channel, then your example works fine. I'm not sure what the actual question is. – JimB May 23 '18 at 13:33
  • 1
    Note that in your example one would normally just signal the goroutine by closing the channel: https://play.golang.org/p/svrXcmiHymx Though if you're only going to have something execute once, what's the point of trying to have some sort of cancelation? – JimB May 23 '18 at 13:38
  • 1
    Use the `sync` package (`sync.Once` specifically) to have something that will only be executed once. Use that in tandem with other channel logic to have a channel that signals something only once. – Lansana Camara May 23 '18 at 13:39
  • @JimB yeah, i only want to execute the real work once, and the point is i also want to notify the goroutine to quit unconditionally based on something happened outside, like in http handler client close the connection before the response is ready. So i guess the notify once is the valid approach, but will it potentially create some issue? and also does go has some built-in mechanism to generate one time signal? – lnshi May 23 '18 at 13:45
  • 1
    @Inshi, the point is that if you're only executing something once, there's nothing to quit, since you can return immediately after the work is done. – JimB May 23 '18 at 13:47
  • @JimB u still didn't get it, quit it unconditionally means force to stop it before it completing all its works, means: don't do the remain work, just quit – lnshi May 23 '18 at 13:50
  • 2
    @lnshi, You can't interrupt a blocking call. You either wait for it to complete, or rewrite it so that it can be interrupted. This is what a [`Context`](https://golang.org/pkg/context/#Context) is usually used for. – JimB May 23 '18 at 13:54
  • @JimB yes, i am exactly looking for a full solution for using the `Context` package, maybe u can refer to this question i just asked: https://stackoverflow.com/questions/50484914/is-the-go-http-handler-goroutine-expected-to-exit-immediately-in-this-case and post ur answer? after that question even i monitored the context.cancel() i still cannot find a way to terminate my handler immediately. – lnshi May 23 '18 at 14:04
  • 1
    @Inshi, your previous question has the correct answer already, I don't see anything I can add. I think you need to provide a concrete example of the problem you're having. – JimB May 23 '18 at 14:12
  • @JimB I added my full thinking here: https://play.golang.org/p/7xjjiW7XdoQ, the answer of my previous question perfectly addressed my concern, also perfectly handled the timeout/deadline problem, but i found i still don't exactly know what to do with the cancellation problem, as u saw in the code, i want to achieve when client close connection my handler just simply quit, release all resources, don't do any remain work – lnshi May 23 '18 at 14:39
  • @Inshi, first, that example is invalid because you can't asynchronously use an http.ResponseWriter and return from the handler, so you need to block. If you want `doStuff` to be interruptible in more places, you continue passing the cancelation context down through the calls to the grpc requests. – JimB May 23 '18 at 14:48

1 Answers1

1

Instead of using for select loop you want to check for the cancel signal in multiple places. But in order to not block you still have to use select (with empty default case) which makes it a bit akward. I'm using the context.Context instead of "plain cancel channel" but the idea is the same:

func doWork(ctx context.Context) {
    fmt.Println("Doing some work 1")
    time.Sleep(time.Second * 5)
    // check is the task cancelled
    select {
    case <-ctx.Done():
        fmt.Println("cancelled at checkpoint 1")
        return
    default:
    }
    fmt.Println("Doing some work 2")
    time.Sleep(time.Second * 5)
    // check is the task cancelled
    select {
    case <-ctx.Done():
        fmt.Println("cancelled at checkpoint 2")
        return
    default:
    }
    fmt.Println("Doing some work 3")
    time.Sleep(time.Second * 5)
}

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    defer cancel()
    go func() {
        doWork(ctx)
        wg.Done()
    }()
    wg.Wait()
    fmt.Println("done")
}
ain
  • 22,394
  • 3
  • 54
  • 74
  • thanks for ur answer, multiple checkpoints, i don't think we should have similar code in production :) – lnshi May 23 '18 at 14:41
  • If you want to be able to abandon lengthy task "whenever it is cancelled" there is no other way than to select points in the task where it makes sense to check for the cancel signal and act appropriately (ie return without completing rest of the task). – ain May 23 '18 at 15:01