mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 06:08:23 +08:00
* Add Yubikey PrivateKey implementation for use by Teleport clients. - Add yubikey login logic, reusing previously stored private keys. - Fix identity file decoding with PIV keys, which sign ecdsa certificates. - Add libpcsclite-dev pre-req for building on linux. - Remove unnecessary keys.Signer interface and move its functionality to keys.PrivateKey. - Move retry and jitter utils to new api/utils/retryutils package.
162 lines
4.6 KiB
Go
162 lines
4.6 KiB
Go
/*
|
|
Copyright 2021 Gravitational, Inc.
|
|
|
|
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 interval
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gravitational/teleport/api/utils/retryutils"
|
|
)
|
|
|
|
// Interval functions similarly to time.Ticker, with the added benefit of being
|
|
// able to specify a custom duration for the first "tick", and an optional
|
|
// per-tick jitter. When attempting to stagger periodic operations it is recommended
|
|
// to apply a large jitter to the first duration, and provide a small jitter for the
|
|
// per-tick jitter. This will ensure that operations started at similar times will
|
|
// have varying initial interval states, while minimizing the amount of extra work
|
|
// introduced by the per-tick jitter.
|
|
type Interval struct {
|
|
cfg Config
|
|
ch chan time.Time
|
|
reset chan struct{}
|
|
closeOnce sync.Once
|
|
done chan struct{}
|
|
}
|
|
|
|
// Config configures an interval. The only required parameter is
|
|
// the Duration field which *must* be a positive duration.
|
|
type Config struct {
|
|
// Duration is the duration on which the interval "ticks" (if a jitter is
|
|
// applied, this represents the upper bound of the range).
|
|
Duration time.Duration
|
|
|
|
// FirstDuration is an optional special duration to be used for the first
|
|
// "tick" of the interval. This duration is not jittered.
|
|
FirstDuration time.Duration
|
|
|
|
// Jitter is an optional jitter to be applied to each step of the interval.
|
|
// It is usually preferable to use a smaller jitter (e.g. NewSeventhJitter())
|
|
// for this parameter, since periodic operations are typically costly and the
|
|
// effect of the jitter is cumulative.
|
|
Jitter retryutils.Jitter
|
|
}
|
|
|
|
// NewNoop creates a new interval that will never fire.
|
|
func NewNoop() *Interval {
|
|
return &Interval{
|
|
ch: make(chan time.Time, 1),
|
|
done: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
// New creates a new interval instance. This function panics on non-positive
|
|
// interval durations (equivalent to time.NewTicker).
|
|
func New(cfg Config) *Interval {
|
|
if cfg.Duration <= 0 {
|
|
panic(errors.New("non-positive interval for interval.New"))
|
|
}
|
|
|
|
interval := &Interval{
|
|
ch: make(chan time.Time, 1),
|
|
cfg: cfg,
|
|
reset: make(chan struct{}),
|
|
done: make(chan struct{}),
|
|
}
|
|
|
|
firstDuration := cfg.FirstDuration
|
|
if firstDuration == 0 {
|
|
firstDuration = interval.duration()
|
|
}
|
|
|
|
// start the timer in this goroutine to improve
|
|
// consistency of first tick.
|
|
timer := time.NewTimer(firstDuration)
|
|
|
|
go interval.run(timer)
|
|
|
|
return interval
|
|
}
|
|
|
|
// Stop permanently stops the interval. Note that stopping an interval does not
|
|
// close its output channel. This is done in order to prevent concurrent stops
|
|
// from generating erroneous "ticks" and is consistent with the behavior of
|
|
// time.Ticker.
|
|
func (i *Interval) Stop() {
|
|
i.closeOnce.Do(func() {
|
|
close(i.done)
|
|
})
|
|
}
|
|
|
|
// Reset resets the interval without pausing it (i.e. it will now fire in
|
|
// jitter(duration) regardless of current timer progress).
|
|
func (i *Interval) Reset() {
|
|
select {
|
|
case i.reset <- struct{}{}:
|
|
case <-i.done:
|
|
}
|
|
}
|
|
|
|
// Next is the channel over which the intervals are delivered.
|
|
func (i *Interval) Next() <-chan time.Time {
|
|
return i.ch
|
|
}
|
|
|
|
// duration gets the duration of the interval. Each call applies the jitter
|
|
// if one was supplied.
|
|
func (i *Interval) duration() time.Duration {
|
|
if i.cfg.Jitter == nil {
|
|
return i.cfg.Duration
|
|
}
|
|
return i.cfg.Jitter(i.cfg.Duration)
|
|
}
|
|
|
|
func (i *Interval) run(timer *time.Timer) {
|
|
defer timer.Stop()
|
|
|
|
// we take advantage of the fact that sends on nil channels never complete,
|
|
// and only set ch when tick is valid and needs to be sent.
|
|
var tick time.Time
|
|
var ch chan<- time.Time
|
|
for {
|
|
select {
|
|
case tick = <-timer.C:
|
|
// timer has fired, reset to next duration and ensure that
|
|
// output channel is set.
|
|
timer.Reset(i.duration())
|
|
ch = i.ch
|
|
case <-i.reset:
|
|
// stop and drain timer
|
|
if !timer.Stop() {
|
|
<-timer.C
|
|
}
|
|
// re-set the timer
|
|
timer.Reset(i.duration())
|
|
// ensure we don't send any pending ticks
|
|
ch = nil
|
|
case ch <- tick:
|
|
// tick has been sent, set ch back to nil to prevent
|
|
// double-send and wait for next timer firing
|
|
ch = nil
|
|
case <-i.done:
|
|
// interval has been stopped.
|
|
return
|
|
}
|
|
}
|
|
}
|