in pkg/client/client_v2.go [765:804]
func (c *clientV2) startActions() {
c.wg.Add(1)
// results are held outside of the retry loop, because on re-connect
// we still want to send the responses that either failed or haven't been
// sent back to the agent.
actionResults := make(chan *proto.ActionResponse, 100)
go func() {
defer c.wg.Done()
// If the initial RPC connection fails, actionRoundTrip
// returns immediately, so we set a timer to avoid spinlocking
// on the error.
retryInterval := 1 * time.Second
retryTimer := time.NewTimer(retryInterval)
for c.ctx.Err() == nil {
actionDuration := durationOf(func() {
c.actionRoundTrip(actionResults)
})
if actionDuration < time.Second {
// If the action round trip lasted less than a second, we're
// probably in an error loop -- apply exponential backoff up to 30s.
retryInterval = nextExpBackoff(retryInterval, 30*time.Second)
} else {
retryInterval = 1 * time.Second
}
// After the RPC client closes, wait until either the timer interval
// expires or the context is cancelled. Note that the timer waits
// one second since the checkinRoundTrip was last _called_, not
// since it returns; immediate retries are ok after an active
// connection shuts down.
select {
case <-retryTimer.C:
retryTimer.Reset(retryInterval)
case <-c.ctx.Done():
}
}
}()
}