0

I am trying to implement a stop to a loop in Go. The inspiration from the code I have right now is from here: how to kill goroutine

However, I've not been able to get my code to behave as expected. The simplified version of my code from a complex code base is this:

package main

import (
    "fmt"
    "time"
)

var quit chan struct{}

var send chan int

func quitroutine() {
    for {
        select {
        case cnt := <-send:
            fmt.Println(cnt)
            if cnt == 5 {
                quit <- struct{}{}
            }
        }
    }
}

func runLoop() {
    cnt := 0
    for {
        select {
        case <-quit:
            fmt.Println("quit!")
        default:
            fmt.Println("default")
        }
        fmt.Println("inloop")
        time.Sleep(1 * time.Second)
        cnt++
        send <- cnt
    }
}

func main() {
    quit = make(chan struct{})
    send = make(chan int)
    go quitroutine()
    runLoop()
    fmt.Println("terminated")
}

This code crashes:

default
inloop
5
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.runLoop()
    /tmp/t.go:37 +0x1a6
main.main()
    /tmp/t.go:45 +0xa4

goroutine 5 [chan send]:
main.quitroutine()
    /tmp/t.go:18 +0x10e
created by main.main
    /tmp/t.go:44 +0x9f
exit status 2

Questions:

  1. Why does it crash at all after cnt is 5? quitroutine only writes to the quitchannel if cnt == 5, but doesn't terminate itself. While runLoop, if it receives on the quit channel, should just print "quit!" (which it doesn't), but not terminate itself.

  2. Why don't I get the "quit!" output? Do I even get the quit channel?

  3. How does this need to be implemented correctly

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
transient_loop
  • 5,984
  • 15
  • 58
  • 117
  • You aren't any `break` or `return` statements, which is what will get you out of the loops. – RayfenWindspear Sep 07 '17 at 19:09
  • @Flimzy in `main()`, I am executing `go quitroutine()`. My assumption is this that in `quitroutine()`I maintain a counter, and when the counter gets to 5, it sends to `quit` in order to stop `runLoop()`, which then would terminate the complete program. – transient_loop Sep 07 '17 at 19:10
  • @RayfenWindspear I had `return` (and `break`) in the `quit` case before, but I removed it for debuggin purposes – transient_loop Sep 07 '17 at 19:11

2 Answers2

2

Just as Adrian has said, one of your goroutines is trying to send on quit, while the other is trying to send on send. To answer your questions:

  1. When cnt == 5 the quitroutine begins attempting to send on quit. Because quit <- struct{}{} is not a select case, the goroutine will block until another tries to read from quit. The other goroutine is similarly stuck trying to do send <- cnt (when cnt = 6).

  2. You never get the "quit!" output because that goroutine is stuck trying to do send <-cnt.

  3. The simplest solution I see would be to adjust runLoop() so that the send <- cnt is a case in the select.

I'd change runLoop() to look like this:

func runLoop() {
    cnt := 0
    for {
        select {
        case <-quit:
            fmt.Println("quit!")
        case send <- cnt: // moved stuff here
            fmt.Println("inloop")
            time.Sleep(1 * time.Second)
            cnt++
        default:
            fmt.Println("default")
        }
        // stuff used to be here
    }
}

This gives me output (until I killed the program):

default
inloop
0
inloop
1
inloop
2
inloop
3
inloop
4
inloop
5
quit!
default
inloop
6
inloop
7

Which seems to mostly be what you were after.

I'd also like to note that the select block in quitroutine() is unnecessary because it only has one case. Cleaning that up may make it more clear that that goroutine is stuck trying to send, and never takes the input from the send channel.

1

When you try to send on the quit channel, the quitroutine blocks until something reads from it.

At the same time, the main routine, in runloop, is trying to send the next number on the send channel. This also blocks, because the routine that would be reading from it is currently blocked trying to send on the quit channel.

Both routines are blocked, which is a deadlock, so the program crashes.

This could be fixed by either putting one or both channel send in a select, or making one or both channels buffered (even a buffer length of 1 would suffice).

Adrian
  • 42,911
  • 6
  • 107
  • 99