2024-09-20 17:54:17 +03:00
|
|
|
package jitter
|
2021-08-04 00:37:56 +03:00
|
|
|
|
|
|
|
import (
|
2022-03-26 17:59:19 +03:00
|
|
|
"context"
|
2021-08-04 00:37:56 +03:00
|
|
|
"time"
|
|
|
|
|
2021-10-02 19:55:07 +03:00
|
|
|
"go.unistack.org/micro/v3/util/rand"
|
2021-08-04 00:37:56 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Ticker is similar to time.Ticker but ticks at random intervals between
|
|
|
|
// the min and max duration values (stored internally as int64 nanosecond
|
|
|
|
// counts).
|
|
|
|
type Ticker struct {
|
2022-03-26 17:59:19 +03:00
|
|
|
ctx context.Context
|
2021-08-04 00:37:56 +03:00
|
|
|
done chan chan struct{}
|
2022-03-26 17:59:19 +03:00
|
|
|
C chan time.Time
|
2021-08-04 00:37:56 +03:00
|
|
|
min int64
|
|
|
|
max int64
|
2022-03-26 17:59:19 +03:00
|
|
|
exit bool
|
2021-08-04 00:37:56 +03:00
|
|
|
rng rand.Rand
|
|
|
|
}
|
|
|
|
|
2022-03-26 17:59:19 +03:00
|
|
|
// NewTickerContext returns a pointer to an initialized instance of the Ticker.
|
|
|
|
// It works like NewTicker except that it has ability to close via context.
|
|
|
|
// Also it works fine with context.WithTimeout to handle max time to run ticker.
|
|
|
|
func NewTickerContext(ctx context.Context, min, max time.Duration) *Ticker {
|
|
|
|
ticker := &Ticker{
|
|
|
|
C: make(chan time.Time),
|
|
|
|
done: make(chan chan struct{}),
|
|
|
|
min: min.Nanoseconds(),
|
|
|
|
max: max.Nanoseconds(),
|
|
|
|
ctx: ctx,
|
|
|
|
}
|
|
|
|
go ticker.run()
|
|
|
|
return ticker
|
|
|
|
}
|
|
|
|
|
2021-08-04 00:37:56 +03:00
|
|
|
// NewTicker returns a pointer to an initialized instance of the Ticker.
|
|
|
|
// Min and max are durations of the shortest and longest allowed
|
|
|
|
// ticks. Ticker will run in a goroutine until explicitly stopped.
|
|
|
|
func NewTicker(min, max time.Duration) *Ticker {
|
|
|
|
ticker := &Ticker{
|
|
|
|
C: make(chan time.Time),
|
|
|
|
done: make(chan chan struct{}),
|
|
|
|
min: min.Nanoseconds(),
|
|
|
|
max: max.Nanoseconds(),
|
2022-03-26 17:59:19 +03:00
|
|
|
ctx: context.Background(),
|
2021-08-04 00:37:56 +03:00
|
|
|
}
|
|
|
|
go ticker.run()
|
|
|
|
return ticker
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop terminates the ticker goroutine and closes the C channel.
|
|
|
|
func (ticker *Ticker) Stop() {
|
2022-03-26 17:59:19 +03:00
|
|
|
if ticker.exit {
|
|
|
|
return
|
|
|
|
}
|
2021-08-04 00:37:56 +03:00
|
|
|
c := make(chan struct{})
|
|
|
|
ticker.done <- c
|
|
|
|
<-c
|
2022-03-26 17:59:19 +03:00
|
|
|
// close(ticker.C)
|
|
|
|
ticker.exit = true
|
2021-08-04 00:37:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ticker *Ticker) run() {
|
|
|
|
defer close(ticker.C)
|
|
|
|
t := time.NewTimer(ticker.nextInterval())
|
|
|
|
for {
|
|
|
|
// either a stop signal or a timeout
|
|
|
|
select {
|
2022-03-26 17:59:19 +03:00
|
|
|
case <-ticker.ctx.Done():
|
|
|
|
t.Stop()
|
2021-08-04 00:37:56 +03:00
|
|
|
case c := <-ticker.done:
|
|
|
|
t.Stop()
|
|
|
|
close(c)
|
|
|
|
return
|
|
|
|
case <-t.C:
|
|
|
|
select {
|
|
|
|
case ticker.C <- time.Now():
|
|
|
|
t.Stop()
|
|
|
|
t = time.NewTimer(ticker.nextInterval())
|
|
|
|
default:
|
|
|
|
// there could be noone receiving...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ticker *Ticker) nextInterval() time.Duration {
|
|
|
|
return time.Duration(ticker.rng.Int63n(ticker.max-ticker.min)+ticker.min) * time.Nanosecond
|
|
|
|
}
|