I need that the Golang scheduler run all goroutines before continue, runtime.Gosched() does not solve.
The problem is that the go routine can run so fast that the "select" in start() run after the "select" inside de stopStream(), then the "case <-chanStopStream:" receiver is not ready to the sender "case retChan <- true:". The result is that when this happen the result is the same behavior from when stopStream() hangs
Run this code https://go.dev/play/p/DQ85XqjU2Q_z many times that you will see this both responses Expected response when NOT hang:
2009/11/10 23:00:00 Start
2009/11/10 23:00:00 receive chan
2009/11/10 23:00:03 end
Expected response when hang, but not when is so fast:
2009/11/10 23:00:00 Start
2009/11/10 23:00:00 default
2009/11/10 23:00:01 TIMER
2009/11/10 23:00:04 end
The code
package main
import (
"log"
"runtime"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
wg.Add(1)
//run multiples routines on a huge system
go start()
wg.Wait()
}
func start() {
log.Println("Start")
chanStopStream := make(chan bool)
go stopStream(chanStopStream)
select {
case <-chanStopStream:
log.Println("receive chan")
case <-time.After(time.Second): //if stopStream hangs do not wait more than 1 second
log.Println("TIMER")
//call some crash alert
}
time.Sleep(3 * time.Second)
log.Println("end")
wg.Done()
}
func stopStream(retChan chan bool) {
//do some work that can be faster then caller or not
runtime.Gosched()
//time.Sleep(time.Microsecond) //better solution then runtime.Gosched()
//time.Sleep(2 * time.Second) //simulate when this routine hangs more than 1 second
select {
case retChan <- true:
default: //select/default is used because if the caller times out this routine will hangs forever
log.Println("default")
}
}
There is not a way to run all other goroutines before continuing execution of the current goroutine.
Fix the problem by ensuring that the goroutine does not block on stopStream
:
Option 1: Change chanStopStream
to a buffered channel. This ensures that stopStream
can send a value without blocking.
func start() {
log.Println("Start")
chanStopStream := make(chan bool, 1) // <--- buffered channel
go stopStream(chanStopStream)
...
}
func stopStream(retChan chan bool) {
...
// Always send. No select/default needed.
retChan <- true
}
https://go.dev/play/p/xWg42TO_fIW
Option 2: Close the channel instead of sending a value. A channel can always be closed by the sender. Receive on a closed channel returns the zero value for the channel's value type.
func start() {
log.Println("Start")
chanStopStream := make(chan bool) // buffered channel not required
go stopStream(chanStopStream)
...
func stopStream(retChan chan bool) {
...
close(retChan)
}