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
foohas timed out, then willfooever be able to write to channelchor is there a risk the goroutine blocks or panics? - What happens to channel
choncefooWithTimeouthas 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)
}
