The problem arises from the fact that when your monitor goroutine "takes out" (receives) a value (a function) and executes it (this also happens on the monitor goroutine), during its execution it sends a value on the monitored channel.
This in itself would not cause deadlock as when the function is executed (a()
), it is already taken out of the buffered channel, so there is at least one free space in it, so sending a new value on the channel inside a()
could proceed without blocking.
Problem may arise if there are other goroutines which may also send values on the monitored channel, which is your case.
One way to avoid the deadlock is that if the function being executed tries to "put back" (send) a function not in the same goroutine (which is the monitor goroutine), but in a new goroutine, so the monitor goroutine is not blocked:
actions <- func() {
// Send new func value in a new goroutine:
// this will never block the monitor goroutine
go func() {
actions <- func() {
// No deadlock.
}
}()
}
By doing this, the monitor goroutine will not be blocked even if buffer of actions
is full, because sending a value on it will happen in a new goroutine (which may be blocked until there is free space in the buffer).
If you want to avoid always spawning a new goroutine when sending a value on actions
, you may use a select
to first try to send it without spawning a new goroutine. Only when buffer of actions
is full and cannot be sent without blocking, shall you spawn a goroutine for sending to avoid deadlock, which - depending on your actual case may happen very rarely, and in this case it's inevitable anyway to avoid deadlock:
actions <- func() {
newfv := func() { /* do something */ }
// First try to send with select:
select {
case actions <- newfv:
// Success!
default:
// Buffer is full, must do it in new goroutine:
go func() {
actions <- newfv
}()
}
}
If you have to do this in many places, it's recommended to create a helper function for it:
func safeSend(fv func()) {
// First try to send with select:
select {
case actions <- fv:
// Success!
default:
// Buffer is full, must do it in new goroutine:
go func() {
actions <- fv
}()
}
}
And using it:
actions <- func() {
safeSend(func() {
// something to do
})
}