mirror of https://github.com/k3s-io/k3s
138 lines
3.6 KiB
Go
138 lines
3.6 KiB
Go
![]() |
/*
|
||
|
Copyright 2017 The Kubernetes Authors.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package util
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
"time"
|
||
![]() |
|
||
|
ktypes "k8s.io/apimachinery/pkg/types"
|
||
![]() |
|
||
|
"github.com/golang/glog"
|
||
![]() |
)
|
||
|
|
||
|
type clock interface {
|
||
|
Now() time.Time
|
||
|
}
|
||
|
|
||
|
type realClock struct{}
|
||
|
|
||
|
func (realClock) Now() time.Time {
|
||
|
return time.Now()
|
||
|
}
|
||
|
|
||
|
// backoffEntry is single threaded. in particular, it only allows a single action to be waiting on backoff at a time.
|
||
|
// It is expected that all users will only use the public TryWait(...) method
|
||
|
// It is also not safe to copy this object.
|
||
|
type backoffEntry struct {
|
||
|
backoff time.Duration
|
||
|
lastUpdate time.Time
|
||
|
reqInFlight int32
|
||
|
}
|
||
|
|
||
|
// tryLock attempts to acquire a lock via atomic compare and swap.
|
||
|
// returns true if the lock was acquired, false otherwise
|
||
|
func (b *backoffEntry) tryLock() bool {
|
||
|
return atomic.CompareAndSwapInt32(&b.reqInFlight, 0, 1)
|
||
|
}
|
||
|
|
||
|
// unlock returns the lock. panics if the lock isn't held
|
||
|
func (b *backoffEntry) unlock() {
|
||
|
if !atomic.CompareAndSwapInt32(&b.reqInFlight, 1, 0) {
|
||
|
panic(fmt.Sprintf("unexpected state on unlocking: %+v", b))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// TryWait tries to acquire the backoff lock, maxDuration is the maximum allowed period to wait for.
|
||
|
func (b *backoffEntry) TryWait(maxDuration time.Duration) bool {
|
||
|
if !b.tryLock() {
|
||
|
return false
|
||
|
}
|
||
|
defer b.unlock()
|
||
|
b.wait(maxDuration)
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (entry *backoffEntry) getBackoff(maxDuration time.Duration) time.Duration {
|
||
|
duration := entry.backoff
|
||
|
newDuration := time.Duration(duration) * 2
|
||
|
if newDuration > maxDuration {
|
||
|
newDuration = maxDuration
|
||
|
}
|
||
|
entry.backoff = newDuration
|
||
![]() |
glog.V(4).Infof("Backing off %s", duration.String())
|
||
![]() |
return duration
|
||
|
}
|
||
|
|
||
|
func (entry *backoffEntry) wait(maxDuration time.Duration) {
|
||
|
time.Sleep(entry.getBackoff(maxDuration))
|
||
|
}
|
||
|
|
||
|
type PodBackoff struct {
|
||
|
perPodBackoff map[ktypes.NamespacedName]*backoffEntry
|
||
|
lock sync.Mutex
|
||
|
clock clock
|
||
|
defaultDuration time.Duration
|
||
|
maxDuration time.Duration
|
||
|
}
|
||
|
|
||
|
func (p *PodBackoff) MaxDuration() time.Duration {
|
||
|
return p.maxDuration
|
||
|
}
|
||
|
|
||
|
func CreateDefaultPodBackoff() *PodBackoff {
|
||
|
return CreatePodBackoff(1*time.Second, 60*time.Second)
|
||
|
}
|
||
|
|
||
|
func CreatePodBackoff(defaultDuration, maxDuration time.Duration) *PodBackoff {
|
||
|
return CreatePodBackoffWithClock(defaultDuration, maxDuration, realClock{})
|
||
|
}
|
||
|
|
||
|
func CreatePodBackoffWithClock(defaultDuration, maxDuration time.Duration, clock clock) *PodBackoff {
|
||
|
return &PodBackoff{
|
||
|
perPodBackoff: map[ktypes.NamespacedName]*backoffEntry{},
|
||
|
clock: clock,
|
||
|
defaultDuration: defaultDuration,
|
||
|
maxDuration: maxDuration,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (p *PodBackoff) GetEntry(podID ktypes.NamespacedName) *backoffEntry {
|
||
|
p.lock.Lock()
|
||
|
defer p.lock.Unlock()
|
||
|
entry, ok := p.perPodBackoff[podID]
|
||
|
if !ok {
|
||
|
entry = &backoffEntry{backoff: p.defaultDuration}
|
||
|
p.perPodBackoff[podID] = entry
|
||
|
}
|
||
|
entry.lastUpdate = p.clock.Now()
|
||
|
return entry
|
||
|
}
|
||
|
|
||
|
func (p *PodBackoff) Gc() {
|
||
|
p.lock.Lock()
|
||
|
defer p.lock.Unlock()
|
||
|
now := p.clock.Now()
|
||
|
for podID, entry := range p.perPodBackoff {
|
||
|
if now.Sub(entry.lastUpdate) > p.maxDuration {
|
||
|
delete(p.perPodBackoff, podID)
|
||
|
}
|
||
|
}
|
||
|
}
|