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 willfoo
ever be able to write to channelch
or is there a risk the goroutine blocks or panics? - What happens to channel
ch
oncefooWithTimeout
has exited? - Is this code potentially problematic?
- Should I add
defer close(ch)
withingo func(){...}()
just before callingfoo
? - 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)
}