I have written a simple API client for my app in Go. The client is very simple, it wraps http.Client and has some convenience methods.
The one method I am trying to get some feedback on, does endpoint polling with timeout. It blocks, and returns when either a timeout is reached or status returned by API is successful or in error.
My questions are in comments of the function itself. I have omitted some parts (structs, the "constructor method") that are not relevant to discussion.
caller (in main)
log.Printf("polling status, id: %d", sid)
status, err := api.PollStatus(sid, 1*time.Second, 5*time.Second)
if err != nil {
log.Fatalf("PollStatus error: %+v", err)
}
log.Print(status)
the method
// PollStatus will keep 'pinging' the status API until timeout is reached or status returned is either sucesfull or in error.
// NOTES:
// - this function will block the caller
// QUESTIONS:
// - is the design/functionality of this method idiomatic Go or is there a better way?
// - do I need to close ticker and timeout channels? If so how?
// - is it safe to 'defer ticker.Stop()' at the start of goroutine or do I have to call it before each return?
func (client *APIClient) PollStatus(id int, pollInterval time.Duration, pollTimeout time.Duration) (*Status, error) {
var status *Status
var err error
var wg sync.WaitGroup
ticker := time.NewTicker(pollInterval)
timeout := time.After(pollTimeout)
tickerCounter := 0
// increment the WaitGroup counter for number of goroutines to wait for...
wg.Add(1)
go func() {
defer ticker.Stop()
// decrement the counter when the goroutine completes.
defer wg.Done()
for {
select {
case tout := <-timeout:
log.Printf("polling timeout of '%s' reached at '%s'...", pollTimeout, tout.Format(time.RFC3339))
//ticker.Stop()
return
case tick := <-ticker.C:
status, err = client.GetStatus(id)
tickerCounter++
if err != nil {
//ticker.Stop()
return
}
log.Printf("tick #%d at '%ss', %s", tickerCounter, tick.Format("05"), status)
if status.IsErrorStatus() {
err = errors.New("error while polling for status")
//ticker.Stop()
return
}
if status.IsCompletedStatus() {
err = nil
//ticker.Stop()
return
}
}
}
}()
// wait for polling to complete (or time out)
wg.Wait()
if err != nil {
return nil, err
}
if status == nil {
return nil, errors.New("unable to get status")
}
return status, nil
}
context.Context
, you can use a context with timeout out of the box. See the docs \$\endgroup\$