I'm trying to understand contexts and channels in Go, but I'm having trouble wrapping my head around what's happening. Here's some example code.
package main
import (
"context"
"fmt"
"log"
"time"
"golang.org/x/time/rate"
)
func main() {
msgs := make(chan string)
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
limiter := rate.NewLimiter(rate.Every(2*time.Second), 1)
go func(ctx context.Context, limiter *rate.Limiter) {
for {
limiter.Wait(context.Background())
select {
case <-ctx.Done():
log.Printf("finished!")
return
case msg := <-msgs:
log.Printf("receiving a message: %s", msg)
}
}
}(ctx, limiter)
defer close(msgs)
i := 0
for {
msgs <- fmt.Sprintf("sending message %d", i)
i++
if i > 10 {
break
}
}
}
The results I'm getting are non-deterministic. Sometimes the logger prints out three messages, sometimes it's five. Also, the program ends in a deadlock every time:
2021/12/31 02:07:21 receiving a message: sending message 0
2021/12/31 02:07:23 receiving a message: sending message 1
2021/12/31 02:07:25 receiving a message: sending message 2
2021/12/31 02:07:27 receiving a message: sending message 3
2021/12/31 02:07:29 receiving a message: sending message 4
2021/12/31 02:07:29 finished!
fatal error: all goroutines are asleep - deadlock!
So, I guess I have a couple of questions:
Why doesn't my goroutine simply end after one second?
While the goroutine may wait here instead of the select
:
limiter.Wait(context.Background())
Why is there a deadlock? How can I avoid deadlocks of this nature?
It is your main goroutine which is getting stuck. It happens here:
msgs <- fmt.Sprintf("sending message %d", I)
There are no goroutines that would read from msgs
, so it waits forever.
Here is one of the ways to make it work:
package main
import (
"context"
"fmt"
"log"
"time"
"golang.org/x/time/rate"
)
func main() {
msgs := make(chan string)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
limiter := rate.NewLimiter(rate.Every(1*time.Second), 1)
go func() {
for {
limiter.Wait(context.Background())
select {
case <-ctx.Done():
log.Printf("finished!")
return
case msg := <-msgs:
log.Printf("receiving a message: %s", msg)
}
}
}()
defer close(msgs)
for i := 0; i < 100000; i++ {
select {
case msgs <- fmt.Sprintf("sending message %d", i):
case <-ctx.Done():
log.Printf("finished too!")
return
}
}
}