client: avoid unconsumed channel in timer construction (#15215)
* client: avoid unconsumed channel in timer construction This PR fixes a bug introduced in #11983 where a Timer initialized with 0 duration causes an immediate tick, even if Reset is called before reading the channel. The fix is to avoid doing that, instead creating a Timer with a non-zero initial wait time, and then immediately calling Stop. * pr: remove redundant stop
This commit is contained in:
parent
eabbcebdd4
commit
21237d8337
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:bug
|
||||||
|
client: Fixed a bug where tasks would restart without waiting for interval
|
||||||
|
```
|
|
@ -563,7 +563,8 @@ func (tr *TaskRunner) Run() {
|
||||||
// Set the initial task state.
|
// Set the initial task state.
|
||||||
tr.stateUpdater.TaskStateUpdated()
|
tr.stateUpdater.TaskStateUpdated()
|
||||||
|
|
||||||
timer, stop := helper.NewSafeTimer(0) // timer duration calculated JIT
|
// start with a stopped timer; actual restart delay computed later
|
||||||
|
timer, stop := helper.NewStoppedTimer()
|
||||||
defer stop()
|
defer stop()
|
||||||
|
|
||||||
MAIN:
|
MAIN:
|
||||||
|
|
|
@ -3,6 +3,7 @@ package helper
|
||||||
import (
|
import (
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -369,6 +370,9 @@ type StopFunc func()
|
||||||
//
|
//
|
||||||
// Returns the time.Timer and also a StopFunc, forcing the caller to deal
|
// Returns the time.Timer and also a StopFunc, forcing the caller to deal
|
||||||
// with stopping the time.Timer to avoid leaking a goroutine.
|
// with stopping the time.Timer to avoid leaking a goroutine.
|
||||||
|
//
|
||||||
|
// Note: If creating a Timer that should do nothing until Reset is called, use
|
||||||
|
// NewStoppedTimer instead for safely creating the timer in a stopped state.
|
||||||
func NewSafeTimer(duration time.Duration) (*time.Timer, StopFunc) {
|
func NewSafeTimer(duration time.Duration) (*time.Timer, StopFunc) {
|
||||||
if duration <= 0 {
|
if duration <= 0 {
|
||||||
// Avoid panic by using the smallest positive value. This is close enough
|
// Avoid panic by using the smallest positive value. This is close enough
|
||||||
|
@ -386,6 +390,14 @@ func NewSafeTimer(duration time.Duration) (*time.Timer, StopFunc) {
|
||||||
return t, cancel
|
return t, cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewStoppedTimer creates a time.Timer in a stopped state. This is useful when
|
||||||
|
// the actual wait time will computed and set later via Reset.
|
||||||
|
func NewStoppedTimer() (*time.Timer, StopFunc) {
|
||||||
|
t, f := NewSafeTimer(math.MaxInt64)
|
||||||
|
t.Stop()
|
||||||
|
return t, f
|
||||||
|
}
|
||||||
|
|
||||||
// ConvertSlice takes the input slice and generates a new one using the
|
// ConvertSlice takes the input slice and generates a new one using the
|
||||||
// supplied conversion function to covert the element. This is useful when
|
// supplied conversion function to covert the element. This is useful when
|
||||||
// converting a slice of strings to a slice of structs which wraps the string.
|
// converting a slice of strings to a slice of structs which wraps the string.
|
||||||
|
|
|
@ -376,7 +376,7 @@ func TestCheckNamespaceScope(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_NewSafeTimer(t *testing.T) {
|
func TestTimer_NewSafeTimer(t *testing.T) {
|
||||||
t.Run("zero", func(t *testing.T) {
|
t.Run("zero", func(t *testing.T) {
|
||||||
timer, stop := NewSafeTimer(0)
|
timer, stop := NewSafeTimer(0)
|
||||||
defer stop()
|
defer stop()
|
||||||
|
@ -390,6 +390,17 @@ func Test_NewSafeTimer(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTimer_NewStoppedTimer(t *testing.T) {
|
||||||
|
timer, stop := NewStoppedTimer()
|
||||||
|
defer stop()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
must.Unreachable(t)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_ConvertSlice(t *testing.T) {
|
func Test_ConvertSlice(t *testing.T) {
|
||||||
t.Run("string wrapper", func(t *testing.T) {
|
t.Run("string wrapper", func(t *testing.T) {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue