ticker

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 18, 2024 License: MIT Imports: 4 Imported by: 1

README

ticker

GoDoc

Ticker utilities for Golang - extensions of the standard library's time.Ticker. Create constant, linear, or exponential tickers. Optionally configure random jitter, minimum or maximum intervals, and maximum cumulative duration. See the examples_test.go for comprehensive usage.

Install

go get github.com/tkennon/ticker

Usage

Create tickers that fire at linearly or exponentially increasing intervals. A constant ticker is also included that is functionally equivalent to a time.Ticker. Once started, tickers must be stopped in order to clean up resources.

t := ticker.NewExponential(time.Second, 2.0). // Create a ticker starting at one second whose intervals double each time it fires.
    WithMaxInterval(time.Hour)                // Optionally cap the maximum interval to an hour.
    WithJitter(0.1)                           // Optionally add a +/-10% jitter to each interval.

if err := t.Start(); err != nil {
    return err
}
defer t.Stop()

for range t.C {
    // Do something each time the ticker fires. Note that because we have not
    // added a context to the ticker, and we have not specified a maximum total
    // duration through WithMaxDuration(), the ticker will tick forever.
}

API Stability

v1.0.0 is tagged and considered stable.

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Interval added in v1.1.0

type Interval interface {
	// The [*Ticker.Start] method will call Next to determine how long to wait
	// until firing the next ticker.
	Next() time.Duration
}

Interval provides the interval that the next ticker should fire after.

type Ticker

type Ticker struct {
	C chan time.Time
	// contains filtered or unexported fields
}

Ticker holds a channel that delivers `ticks` of a clock at intervals (just like the standard library time.Ticker).

func NewConstant

func NewConstant(interval time.Duration) *Ticker

NewConstant returns a constant ticker, functionally equivalent to the standard library time.Ticker.

Example
package main

import (
	"fmt"
	"time"

	"github.com/tkennon/ticker"
)

func runFiveTimes(t *ticker.Ticker) bool {
	for i := 0; i < 5; i++ {
		then := time.Now()
		now, ok := <-t.C
		if !ok {
			return false
		}
		fmt.Println(now.Sub(then).Round(time.Millisecond))
	}

	return true
}

func main() {
	con := ticker.NewConstant(time.Millisecond)
	if err := con.Start(); err != nil {
		panic(err)
	}
	defer con.Stop()
	if ok := runFiveTimes(con); !ok {
		panic("ticker stopped unexpectedly")
	}

}
Output:

1ms
1ms
1ms
1ms
1ms

func NewExponential

func NewExponential(initial time.Duration, multiplier float32) *Ticker

NewExponential returns an exponential backoff ticker.

Example
package main

import (
	"fmt"
	"time"

	"github.com/tkennon/ticker"
)

func runFiveTimes(t *ticker.Ticker) bool {
	for i := 0; i < 5; i++ {
		then := time.Now()
		now, ok := <-t.C
		if !ok {
			return false
		}
		fmt.Println(now.Sub(then).Round(time.Millisecond))
	}

	return true
}

func main() {
	exp := ticker.NewExponential(time.Millisecond, 2.0)
	if err := exp.Start(); err != nil {
		panic(err)
	}
	defer exp.Stop()
	if ok := runFiveTimes(exp); !ok {
		panic("ticker stopped unexpectedly")
	}

}
Output:

1ms
2ms
4ms
8ms
16ms

func NewLinear

func NewLinear(initial, increment time.Duration) *Ticker

NewLinear returns a linear backoff ticker.

Example
package main

import (
	"fmt"
	"time"

	"github.com/tkennon/ticker"
)

func runFiveTimes(t *ticker.Ticker) bool {
	for i := 0; i < 5; i++ {
		then := time.Now()
		now, ok := <-t.C
		if !ok {
			return false
		}
		fmt.Println(now.Sub(then).Round(time.Millisecond))
	}

	return true
}

func main() {
	lin := ticker.NewLinear(time.Millisecond, time.Millisecond)
	if err := lin.Start(); err != nil {
		panic(err)
	}
	defer lin.Stop()
	if ok := runFiveTimes(lin); !ok {
		panic("ticker stopped unexpectedly")
	}

}
Output:

1ms
2ms
3ms
4ms
5ms

func NewTicker added in v1.1.0

func NewTicker(interval Interval) *Ticker

NewTicker creates a new generic ticker with the given interval. This package provides NewConstant, NewLinear, and NewExponential as convenience initialisers for constant, linear, and exponential backoff tickers, but NewTicker allows the caller to specify any custom interval.

func (*Ticker) Start

func (t *Ticker) Start() error

Start starts the ticker. The ticker's channel will send the current time after an interval has elapsed. Start returns an error if the ticker could not be started due to restrictions imposed in the ticker config (e.g. minimum allowed interval is greater than the maximum allowed interval). The ticker's channel will fire at different intervals determined by the type of ticker (e.g. constant, linear, or exponential) and by the ticker modifiers (WithJitter, WithMaximum etc). The ticker must be stopped (either explicitly via Stop, or through a context expiring or a maximum duration being reached) to release all associated resources. The ticker's channel will be closed when the ticker is stopped . As such, callers must always check the ticker's channel is still open when receiving "ticks". The ticker will not fire again until its channel has been read.

Note that once started, tickers must not be modified (through the WithXxx modifiers).

func (*Ticker) Stop

func (t *Ticker) Stop()

Stop turns off a ticker. Unlike the standard library, Stop does close the ticker's channel. As such, callers must always check the ticker's channel is still open when receiving "ticks". The ticker may continue to return valid ticks for a brief period after Stop is called.

Once stopped, a ticker will panic if (re)started. Stop will panic if called more than once. It is safe to stop a ticker via different methods (e.g. calling Stop explicitly, canceling a context, or capping the total maximum duration the ticker has ticked for).

Example
package main

import (
	"fmt"
	"time"

	"github.com/tkennon/ticker"
)

func main() {
	con := ticker.NewConstant(time.Millisecond)
	con.Stop()
	if err := con.Start(); err != nil {
		panic(err)
	}
	now, ok := <-con.C
	fmt.Println("ticker ok:", ok)
	fmt.Println("ticker time is zero:", now.IsZero())

}
Output:

ticker ok: false
ticker time is zero: true

func (*Ticker) WithContext

func (t *Ticker) WithContext(ctx context.Context) *Ticker

WithContext adds a context.Context to the ticker. If the context expires then the ticker's channel will be closed.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/tkennon/ticker"
)

func main() {
	ctx, cancel := context.WithCancel(context.Background())
	con := ticker.NewConstant(time.Millisecond).WithContext(ctx)
	then := time.Now()
	if err := con.Start(); err != nil {
		panic(err)
	}
	now, ok := <-con.C
	fmt.Println("ticker ok:", ok)
	fmt.Println(now.Sub(then).Round(time.Millisecond))
	cancel()
	now, ok = <-con.C
	fmt.Println("ticker ok:", ok)
	fmt.Println("ticker time is zero:", now.IsZero())

}
Output:

ticker ok: true
1ms
ticker ok: false
ticker time is zero: true

func (*Ticker) WithFunc

func (t *Ticker) WithFunc(f func()) *Ticker

WithFunc will execute f in its own goroutine each time the ticker fires. The ticker must be stopped to prevent running f.

Example
package main

import (
	"fmt"
	"time"

	"github.com/tkennon/ticker"
)

func runFiveTimes(t *ticker.Ticker) bool {
	for i := 0; i < 5; i++ {
		then := time.Now()
		now, ok := <-t.C
		if !ok {
			return false
		}
		fmt.Println(now.Sub(then).Round(time.Millisecond))
	}

	return true
}

func main() {
	str := make(chan string)
	con := ticker.NewConstant(time.Millisecond).WithFunc(func() {
		str <- "hello"
	})
	if err := con.Start(); err != nil {
		panic(err)
	}
	if ok := runFiveTimes(con); !ok {
		panic("ticker stopped unexpectedly")
	}

	for i := 0; i < 5; i++ {
		fmt.Println(<-str)
	}

}
Output:

1ms
1ms
1ms
1ms
1ms
hello
hello
hello
hello
hello

func (*Ticker) WithJitter

func (t *Ticker) WithJitter(fraction float64) *Ticker

WithJitter adds a uniformly random jitter to the time the ticker next fires. The jitter will be within `fraction` of the next interval. For example, WithJitter(0.1) applies a 10% jitter, so a linear ticker that would otherwise fire after 1, 2, 3, 4, ... seconds would now fire between 0.9-1.1 seconds on the first tick, and 1.8-2.2 seconds on the second tick, and so forth. The jitter fraction may be greater than one, allowing the possiblity for jittered tickers to fire immediately if the calculated interval with the jitter is less than zero. Note that jitter is symmetric; a negative `fraction` is treated the same as the corresponding positive value.

func (*Ticker) WithMaxDuration

func (t *Ticker) WithMaxDuration(d time.Duration) *Ticker

WithMaxDuration sets the maxiumum total duration over all ticks that the ticker will run for. Once the maximum duration is reached, the ticker's channel will be closed.

Example
package main

import (
	"fmt"
	"time"

	"github.com/tkennon/ticker"
)

func runFiveTimes(t *ticker.Ticker) bool {
	for i := 0; i < 5; i++ {
		then := time.Now()
		now, ok := <-t.C
		if !ok {
			return false
		}
		fmt.Println(now.Sub(then).Round(time.Millisecond))
	}

	return true
}

func main() {
	exp := ticker.NewExponential(time.Millisecond, 2.0).WithMaxDuration(10 * time.Millisecond)
	if err := exp.Start(); err != nil {
		panic(err)
	}
	ok := runFiveTimes(exp)
	fmt.Println("ticker channel closed:", !ok)

}
Output:

1ms
2ms
4ms
ticker channel closed: true

func (*Ticker) WithMaxInterval

func (t *Ticker) WithMaxInterval(d time.Duration) *Ticker

WithMaxInterval sets the maximum interval between times the ticker fires. This is applied after jitter is applied.

Example
package main

import (
	"fmt"
	"time"

	"github.com/tkennon/ticker"
)

func runFiveTimes(t *ticker.Ticker) bool {
	for i := 0; i < 5; i++ {
		then := time.Now()
		now, ok := <-t.C
		if !ok {
			return false
		}
		fmt.Println(now.Sub(then).Round(time.Millisecond))
	}

	return true
}

func main() {
	lin := ticker.NewLinear(time.Millisecond, time.Millisecond).WithMaxInterval(3 * time.Millisecond)
	if err := lin.Start(); err != nil {
		panic(err)
	}
	defer lin.Stop()
	if ok := runFiveTimes(lin); !ok {
		panic("ticker stopped unexpectedly")
	}

}
Output:

1ms
2ms
3ms
3ms
3ms

func (*Ticker) WithMinInterval

func (t *Ticker) WithMinInterval(d time.Duration) *Ticker

WithMinInterval sets the minimum interval between times the ticker fires. This is applied after jitter is applied.

Example
package main

import (
	"fmt"
	"time"

	"github.com/tkennon/ticker"
)

func runFiveTimes(t *ticker.Ticker) bool {
	for i := 0; i < 5; i++ {
		then := time.Now()
		now, ok := <-t.C
		if !ok {
			return false
		}
		fmt.Println(now.Sub(then).Round(time.Millisecond))
	}

	return true
}

func main() {
	lin := ticker.NewLinear(5*time.Millisecond, -time.Millisecond).WithMinInterval(3 * time.Millisecond)
	if err := lin.Start(); err != nil {
		panic(err)
	}
	defer lin.Stop()
	if ok := runFiveTimes(lin); !ok {
		panic("ticker stopped unexpectedly")
	}

}
Output:

5ms
4ms
3ms
3ms
3ms

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL