2

I have the following setup:

func startsMain (){
    go main ()
}

fun stopMain (){
    //kill main
}

func main() {
    //infinite loop 
}

I am creating cucumber steps and I need to be able to start and shut down the application.

noamt
  • 7,397
  • 2
  • 37
  • 59
Michael
  • 43
  • 1
  • 1
  • 5
  • 3
    You can't stop a goroutine from the "outside". The goroutine has to support some kind of termination signalling (most often a channel). But if it does not, you can't force it or kill it. Possible duplicate of [cancel a blocking operation in Go](http://stackoverflow.com/questions/28240133/cancel-a-blocking-operation-in-go/28240299#28240299). – icza Mar 02 '17 at 16:00
  • 3
    You can't simply kill a goroutine; please show an example of the problem you're trying to solve. – JimB Mar 02 '17 at 16:01
  • @icza, is it possible to use any other way rather than goroutine to start and stop main ? – Michael Mar 02 '17 at 16:03
  • 1
    @Michael: `main` is called implicitly in the main package by the runtime. You can't call it yourself. – JimB Mar 02 '17 at 16:04
  • I mean, if you want to stop main() from outside, you could always call something like os.Exit(). That kills main by killing the app as a whole, and can be called even from outside the "main" goroutine. If you want to stop the main goroutine but leave the other goroutines active, you can only do that from within the main goroutine (but not necessarily the main() function itself), using `runtime.Goexit()`, though this has ancillary side-effects (like the app crashing if all other goroutines exit as well). – Kaedys Mar 02 '17 at 22:06

1 Answers1

6

You can kill you infinite loop using select and channels!

var quit chan struct{}

func startLoop() {
    quit := make(chan struct{})
    go loop()
}

func stopLoop() {
    // As mentioned by Kaedys
    //close(quit)
    // permits signalling everyone havins such a `case <-quit:`
    // statement to be stopped at once, which might be even better.
    quit <- struct{}{}
}

// BTW, you cannot call your function main, it is reserved
func loop() {
    for {
        select {
        case <-quit:
            return # better than break
        default:
            // do stuff. I'd call a function, for clarity:
            do_stuff()
        }
    }
}

Nice piece of Go swap, ain't it?

Now, what is this strange chan struct{}? It is a zero-sized channel. We can only fill it with empty structs (that is: struct{}{}). It could be a chan bool or whatever else, since we don't use the content of the channel. The important point is that we use the quit channel to notify the infinite loop in our goroutine that it is time to stop.

The select statement is used to catch what comes out of channels. It is a blocking statement (that will halt the execution until something is put in one of the channels surveyed by a case), unless you put a default statement. In this situation, every time the select is executed, the loop will break if something was put inside quit, or do_stuff() will be called. You already know this if you've been through the Go Tour.

Other cool concurrency patterns can be found on the Go Blog.

Finally, for further fun, you can ask your do_stuff function to be executed at regular time intervals by using Tickers, instead of consuming 100% CPU, like so:

import "time"

// [...]

func loop() {
    // This ticker will put something in its channel every 2s 
    ticker := time.NewTicker(2 * time.Second)
    // If you don't stop it, the ticker will cause memory leaks
    defer ticker.Stop()
    for {
        select {
        case <-quit:
            return
        case <-ticker.C:
            // do stuff. I'd call a function, for clarity:
            do_stuff()
        }
    }
}

Here, select is blocking, since we removed the default statement.

Adrien Luxey
  • 552
  • 1
  • 6
  • 16
  • 2
    Worth noting that the typical style for quit channels is to `close()` them rather than sending a value over them. Not only is this cleaner in the code, but a closed channel signals _all_ listeners at once, whereas you'd otherwise have to send a separate message over the channel for each listener. Also, `main()` is the program entry point gets called by default, you don't need to (and I believe _can't_) call it separately from another function. – Kaedys Mar 02 '17 at 17:22
  • I heard something about `close()` letting the `case` be executed, which I thought unlogical. But now I get it: signalling everyone at once will be very useful for me, thanks :) – Adrien Luxey Mar 02 '17 at 17:26
  • Even better is the `context` package, see https://golang.org/pkg/context/ You should change your example to use a `ctx, cancel := context.WithCancel(context.Background())`. Then `main` can use a `defer cancel()`, the goroutines can use a `select` on the `ctx.Done()` channel to exit. – Zan Lynx Mar 02 '17 at 17:56
  • nit, there's no such thing as a "zero-sized channel". All channels have the same size in memory regardless of their type. It may be unbuffered if that's what you mean. – JimB Mar 02 '17 at 18:05
  • 1
    @JimB I think he means that the message itself, the `struct{}{}`, is zero size. – Kaedys Mar 02 '17 at 18:35
  • Also, @AdrienLuxey, for your ticker stop, it'd probably be better to `defer ticker.Stop()` immediately after creating it, instead of putting it in the quit case. That way the `ticker.Stop()` call happens even if that goroutine panics for some reason (which is especially important if something else catches that panic and thus prevents the app from crashing). It's a good habit to put things that _must_ happen when a function ends in defers as early as feasible in the function. – Kaedys Mar 02 '17 at 18:37
  • The reason closing a channel works is that: receive from a closed channel returns the zero value immediately, thus you breakout of forever loop. – Akavall Mar 03 '17 at 02:38
  • select would get blocked if the do_stuff() could not return in time. this is not a good practice – Liu Weibo Nov 21 '17 at 06:54
  • @LiuWeibo Well, maybe one wants `do_stuff()` to complete before it exits? Interrupting a function in the middle of its execution would indeed be harder. What do you propose instead? – Adrien Luxey May 31 '18 at 10:44
  • Would this `break` only breaks `select` but not `for`? – nos Nov 08 '18 at 16:46
  • You're right according to the [doc](https://golang.org/ref/spec#Break_statements). I updated my answer with a `return` instead. – Adrien Luxey Dec 04 '18 at 13:45