Search code examples
godeadlockchannelfatal-errorgoroutine

WorkGroup: "fatal error: all goroutines are asleep - deadlock!"


I am trying to wrap goroutines like so:

package task

import "sync"

type NoResult struct {
    wait *sync.WaitGroup
}

type Result[T any] struct {
    channel chan T
    wait    *sync.WaitGroup
}

func RunWithResult[T any](task func() T) *Result[T] {
    waitGroup := sync.WaitGroup{}
    channel := make(chan T)

    waitGroup.Add(1)
    go func(wg *sync.WaitGroup) {
        defer wg.Done()
        defer close(channel)
        result := task()
        channel <- result
    }(&waitGroup)

    return &Result[T]{
        channel: channel,
        wait:    &waitGroup,
    }
}

func Run(task func()) *NoResult {
    waitGroup := sync.WaitGroup{}

    waitGroup.Add(1)
    go func(wg *sync.WaitGroup) {
        defer wg.Done()
        task()
    }(&waitGroup)

    return &NoResult{
        wait: &waitGroup,
    }
}

func (task *Result[T]) Wait() *T {
    task.wait.Wait() //  <- Panics here
    result := <-task.channel
    return &result
}

func (task *NoResult) Wait() {
    task.wait.Wait() // <- Works Fine
}

Here there are two types of result and methods for them.

Here I am calling the functions:

func main() {

    result := task.RunWithResult[int](func() int {
        return 45
    }).Wait()

    fmt.Println(*result)

    task.Run(func() {
        fmt.Println("Hello Worldდდ")
    }).Wait()
}

The problem is that, function with the result panics and throws the following error

fatal error: all goroutines are asleep - deadlock!.

Another function without result works fine. The problem occurs when task.wait.Wait() (also marked in the code) is called. I did not get why this function panics for the first function and works fine for another.

P.S. I know that I can change the first function and just wait for the channel. The behavior will be same, but still I wonder why the problem is caused


Solution

  • The deadlock is:

    • The main goroutine blocks on the wait group. As a result, the main goroutine does not receive from the channel.
    • The task blocks sending to the channel.

    Fix by using a buffered channel:

    func RunWithResult[T any](task func() T) *Result[T] {
        waitGroup := sync.WaitGroup{}
        channel := make(chan T, 1) // <-- buffered
        ...
    

    https://go.dev/play/p/cZnM3eTdl_-

    The buffered channel ensures that the task goroutine can complete, whether main receives from the channel or not.