Home > Mobile >  timeout on function and goroutine leak
timeout on function and goroutine leak

Time:09-06

I wish to put a timeout on a function called foo. Consider the following

func fooWithTimeout(d time.Duration) error {
    ch := make(chan error, 1)
    go func() {
        ch <- foo()
    }()

    select {
    case err := <-ch:
        return err
    case <-time.After(d):
        return errors.New("foo has timed out")
    }
}
  • If foo has timed out, then will foo ever be able to write to channel ch or is there a risk the goroutine blocks or panics?
  • What happens to channel ch once fooWithTimeout has exited?
  • Is this code potentially problematic?
  • Should I add defer close(ch) within go func(){...}() just before calling foo?
  • Does it matter that I use a buffered (with size 1) or an unbuffered channel in this example?

CodePudding user response:

After the timer tick, fooWithTimeout will return. The goroutine will continue running until foo returns.

If foo times out, it will write to channel ch because it is buffered.

The channel ch will be garbage collected eventually if foo returns.

You don't need to close the channel. Once it is out of scope, it will be garbage collected.

A large burst of calls fooWithTimeout will create large amount of resources. Each call creates two goroutines. The proper way of timing this out is to change foo to use a context.

CodePudding user response:

Building on https://stackoverflow.com/a/73611534/1079543, here is foo with a context:

package main

import (
    "context"
    "fmt"
    "log"
    "time"
)

func foo(ctx context.Context) (string, error) {
    ch := make(chan string, 1)

    go func() {
        fmt.Println("Sleeping...")
        time.Sleep(time.Second * 1)
        fmt.Println("Wake up...")
        ch <- "foo"
    }()

    select {
    case <-ctx.Done():
        return "", fmt.Errorf("context cancelled: %w", ctx.Err())
    case result := <-ch:
        return result, nil
    }
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
    defer cancel()

    res, err := foo(ctx)
    if err != nil {
        log.Fatalf("foo failed: %v", err)
    }

    log.Printf("res: %s", res)
}
  • Related