memoryless

package
v0.27.0 Latest Latest
Warning

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

Go to latest
Published: Dec 13, 2023 License: GPL-3.0 Imports: 4 Imported by: 0

Documentation

Overview

Package memoryless helps repeated calls to a function be distributed across time in a memoryless fashion.

Index

Constants

This section is empty.

Variables

View Source
var MakeTicker = NewTicker

MakeTicker is a deprecated alias for NewTicker

Functions

func AfterFunc

func AfterFunc(c Config, f func()) (*time.Timer, error)

AfterFunc constructs a single-shot time.Timer that, if repeatedly used to construct a series of timers, will ensure that the resulting events conform to the memoryless distribution. For more on how this could and should be used, see the comments to Ticker. It is intended to be a drop-in replacement for time.AfterFunc.

func NewTimer

func NewTimer(c Config) (*time.Timer, error)

NewTimer constructs a single-shot time.Timer that, if repeatedly used to construct a series of timers, will ensure that the resulting events conform to the memoryless distribution. For more on how this could and should be used, see the comments to Ticker. It is intended to be a drop-in replacement for time.NewTimer.

func Run

func Run(ctx context.Context, f func(), c Config) error

Run calls the given function repeatedly, using a memoryless.Ticker to wait between function calls. It is a convenience function for code that does not want to use the channel interface.

Types

type Config

type Config struct {
	// Expected records the expected/mean/average amount of time between runs.
	Expected time.Duration
	// Min provides clamping of the randomly produced value. All timers will wait
	// at least Min time.
	Min time.Duration
	// Max provides clamping of the randomly produced value. All timers will take
	// at most Max time.
	Max time.Duration

	// Once is provided as a helper, because frequently for unit testing and
	// integration testing, you only want the "Forever" loop to run once.
	//
	// The zero value of this struct has Once set to false, which means the value
	// only needs to be set explicitly in codepaths where it might be true.
	Once bool
}

Config represents the time we should wait between runs of the function.

A valid config will have:

0 <= Min <= Expected <= Max (or 0 <= Min <= Expected and Max is 0)

If Max is zero or unset, it will be ignored. If Min is zero or unset, it will be ignored.

func (Config) Check

func (c Config) Check() error

Check whether the config contrains sensible values. It return an error if the config makes no mathematical sense, and nil if everything is okay.

type Ticker

type Ticker struct {
	C <-chan time.Time // The channel on which the ticks are delivered.
	// contains filtered or unexported fields
}

Ticker is a struct that waits a config.Expected amount of time on average between sends down the channel C. It has the same interface and requirements as time.Ticker. Every Ticker created must have its Stop() method called or it will leak a goroutine.

The inter-send time is a random variable governed by the exponential distribution and will generate a memoryless (Poisson) distribution of channel reads over time, ensuring that a measurement scheme using this ticker has the PASTA property (Poisson Arrivals See Time Averages). This statistical guarantee is subject to two caveats:

Caveat 1 is that, in a nod to the realities of systems needing to have guarantees, we allow the random wait time to be clamped both above and below. This means that channel events will be at least config.Min and at most config.Max apart in time. This clamping causes bias in the timing. For use of Ticker to be statistically sensible, the clamping should not be too extreme. The exact mathematical meaning of "too extreme" depends on your situation, but a nice rule of thumb is config.Min should be at most 10% of expected and config.Max should be at least 250% of expected. These values mean that less than 10% of time you will be waiting config.Min and less than 10% of the time you will be waiting config.Max.

Caveat 2 is that this assumes that the actions performed between channel reads take negligible time when compared to the expected wait time. Memoryless sequences have the property that the times between successive event starts has the exponential distribution, and the exponential distribution can generate numbers arbitrarily close to zero (albeit exponentially infrequently). This code will not send on the channel if the other end is not ready to receive, which provides another lower bound on inter-event times. The only other option if the other side of the channel is not ready to receive would be queueing events in the channel, and that has some pathological cases we would like to avoid. In particular, queuing can cause long-term correlations if the queue gets big, which is the exact opposite of what a memoryless system is trying to achieve.

func NewTicker

func NewTicker(ctx context.Context, config Config) (*Ticker, error)

NewTicker creates a new memoryless ticker. The returned struct is compatible with the time.Ticker struct interface, and everywhere you use a time.Ticker, you can use a memoryless.Ticker.

func (*Ticker) Stop

func (t *Ticker) Stop()

Stop the ticker goroutine.

Jump to

Keyboard shortcuts

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