Two go routines reading from the same channel. The first go routine never prints its shutdown message after the done
channel is closed, while the second go routine always does.
Why is the message from the first go routine not printing and is the method even returning
?
main.go
func main() {
done := make(chan bool)
c := make(chan os.Signal, 1)
cameras := client.CameraConfig()
client.DrawUserControls(cameras)
operator := client.NewOperator(cameras)
go operator.UserInputListener(done)
go operator.ParseAndExecuteUserCommand(done)
signal.Notify(c, os.Interrupt)
for range c {
close(done)
break
}
log.Println("Interrupt signal received. Shutting client down....")
time.Sleep(5 * time.Second)
}
client.go
func (o *Operator) UserInputListener(done <-chan bool) {
reader := bufio.NewReader(os.Stdin)
for {
select {
case <-done:
log.Println("Keyboard listener shutting down.") // <-- this never prints
return
default:
line, _, err := reader.ReadLine()
if err != nil {
log.Println(err)
}
data := strings.Split(string(line), "")
id, err := strconv.Atoi(data[1])
if err != nil {
log.Println(err)
continue
}
switch data[0] {
case "b":
o.Controls <- Ctrl{
Identifier: id,
Ctrl: "run",
}
case "t":
o.Controls <- Ctrl{
Identifier: id,
Ctrl: "terminate",
}
case "r":
o.Controls <- Ctrl{
Identifier: id,
Ctrl: "record",
}
case "s":
o.Controls <- Ctrl{
Identifier: id,
Ctrl: "stop",
}
}
}
}
}
func (o *Operator) ParseAndExecuteUserCommand(done <-chan bool) {
for {
select {
case <-done:
log.Println("Command operator shutting down.")
return
case ctrl := <-o.Controls:
switch ctrl.Ctrl {
case "run":
o.Room[ctrl.Identifier].Run()
case "terminate":
o.Room[ctrl.Identifier].Close()
case "record":
o.Room[ctrl.Identifier].Write()
case "stop":
o.Room[ctrl.Identifier].Stop()
}
}
}
}
The reason is because you have created synchronous channel and you push here 1 message and then you could read it only once as well. That is because you get only 1 (random) read from done
channel.
The way you can shut down your goroutines is to use WaitGroup
:
main.go:
var (
done chan bool
)
func main() {
cameras := client.CameraConfig()
client.DrawUserControls(cameras)
operator := client.NewOperator(cameras)
done = make(chan bool, 1)
wg := &sync.WaitGroup{}
wg.Add(2)
go operator.UserInputListener(done, wg)
go operator.ParseAndExecuteUserCommand(done, wg)
handleShutdown()
wg.Wait()
}
func handleShutdown() {
ch := make(chan os.Signal, 1)
go func() {
<-ch //wait for application terminating
log.Println("Shutdown received.")
close(done)
}()
signal.Notify(ch, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
}
client.go:
func (o *Operator) UserInputListener(done <-chan bool, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-done:
log.Println("Keyboard listener shutting down.")
return
........
}
}
}
func (o *Operator) ParseAndExecuteUserCommand(done <-chan bool, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-done:
log.Println("Command operator shutting down.")
return
........
}
}
}
Use this link for details
The reason UserInputListener
is not entering case <-done:
is because it is stuck waiting for some input at this line:
line, _, err := reader.ReadLine()
This line is blocking!
This is not easy.
You could use another go routine to do the reading, send the data into a channel that you read from in the select where you also read from the done
channel. That would properly close the UserInputListener
but leave the other goroutine to not be closed properly. But maybe that does not matter as much...?
func (o *Operator) UserInputListener(done <-chan bool) {
// channel with some buffer so reader doesn't have to wait (so much)
ch := make(chan string, 10)
go func() {
reader := bufio.NewReader(os.Stdin)
for {
line, _, err := reader.ReadLine()
if err != nil {
log.Println(err)
// stop on error?
// return
}
ch <- string(line)
}
}()
for {
select {
case <-done:
log.Println("Keyboard listener shutting down.") // <-- this never prints
return
case line:= <-ch:
data := strings.Split(line, "")
id, err := strconv.Atoi(data[1])
if err != nil {
log.Println(err)
continue
}
switch data[0] {
case "b":
o.Controls <- Ctrl{
Identifier: id,
Ctrl: "run",
}
case "t":
o.Controls <- Ctrl{
Identifier: id,
Ctrl: "terminate",
}
case "r":
o.Controls <- Ctrl{
Identifier: id,
Ctrl: "record",
}
case "s":
o.Controls <- Ctrl{
Identifier: id,
Ctrl: "stop",
}
}
}
}
}
You could also try to close whatever the reader
is reading from. I have already used this solution in other contexts like reading from serial devices.
This does not work with os.Stdin
however as JimB pointed out to me. os.StdIn.Close()
will block as it waits for the reader to finish.