timeApi

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jan 24, 2023 License: MIT Imports: 11 Imported by: 0

README

GitHub issues GitHub forks GitHub stars GitHub license Go Codecov branch Go Report Card Documentation

timeApi-go

  • Non global instance wrapper for go timer calls, plus matching fake

Inspired by https://github.com/benbjohnson/clock it worked great for a while until my project had too many bg threads running.

Install

go get -u github.com/Villenny/timeApi-go

Using the Package

The expected use case:

  • just the same as the system time library more or less
import "github.com/villenny/timeApi-go"

// timeApi mirrors all the time calls
timeapi := timeApi.New()
startTime := timeapi.Now()
  • The interface for reference
type TimeApi interface {
	After(d time.Duration) <-chan time.Time
	AfterFunc(d time.Duration, f func()) *Timer
	Now() time.Time
	Since(t time.Time) time.Duration
	Until(t time.Time) time.Duration
	Sleep(d time.Duration)
	Gosched()
	Tick(d time.Duration) <-chan time.Time
	NewTicker(d time.Duration) *Ticker
	NewTimer(d time.Duration) *Timer
	WithDeadline(parent context.Context, d time.Time) (context.Context, context.CancelFunc)
	WithTimeout(parent context.Context, t time.Duration) (context.Context, context.CancelFunc)
}

Using the test fake

Using fakeTimeApi:

  • In general advancing the fake clock will cause sleeps after any timer event to allow bg threads to process, for a surprisingly long time too, since cloud build systems tend to be oversubscribed
import "github.com/villenny/timeApi-go"

// initialize and start the timeApi
timeapi := timeApi.NewFake().Start(time.Date(2009, 11, 17, 20, 34, 58, 0, time.UTC))

// advance the fake clock
timeapi.AdvanceClock(2 * time.Second)

timeapi.Stop()

AssertEventCount(t, timeapi, 0)

Debugging with the fake time event log

  • One of the key features of the fake, is that it allows you to get the serialized list of timer events, including the ability to inject your own events, and then assert the count is what you expect.

  • In the event the assertion fails you will get something like this:

--- FAIL: TestFakeApi (0.11s)
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:750: AssertEventCount: expected 0, got 21
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    0 0000000000000000 newTimer: tp/0/AfterFunc/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:103 60m
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    1 0000000000000000 Stop tp/0/AfterFunc/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:103 isDrained=true
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    2 0000000000000000 Reset tp/0/AfterFunc/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:103 2ms gotTick=false
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    3 0000000000000000 newTimer: tp/1/After/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:113 2ms
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    4 0000000000000000 newTimer: tp/2/Timer/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:120 2ms
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    5 0000000000000000 newTicker: tp/3/Ticker/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:127 60m
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    6 0000000000000000 Stopped: tp/3/Ticker/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:127 drainCount: 0
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    7 0000000000000000 Stopped: tp/3/Ticker/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:127 (Wasnt running)
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    8 0000000000000000 Reset tp/3/Ticker/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:127 2ms
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:    9 0000000000000000 newTicker: tp/4/Tick/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:136 (Always leaks - dont use) 2ms
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   10 0000000000000000 +Sleep: 2ms
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   11 0000000000000002 | DoTick: tp/4/Tick/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:136 (Always leaks - dont use) - 2ms
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   12 0000000000000002 | DoTick: tp/3/Ticker/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:127 - 0ns
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   13 0000000000000002 | DoTick: tp/2/Timer/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:120 - 0ns
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   14 0000000000000002 | DoTick: tp/1/After/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:113 - 0ns
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   15 0000000000000002 | DoTick: tp/0/AfterFunc/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:103 - 0ns
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   16 0000000000000002 -Sleep: 2ms
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   17 0000000000000002 Stopped: tp/3/Ticker/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:127 drainCount: 0
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   18 0000000000000002 +Gosched: 60ns
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   19 0000000000000002 -Gosched: 60ns
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:752:   20 0000000000000002 Leaked Tickproducer: tp/4/Tick/c:/dev/GitHub/Villenny/timeApi-go/fakeTimeApi_test.go:136 (Always leaks - dont use)
    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:755:
            Error Trace:    c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi.go:755
                                        c:\dev\GitHub\Villenny\timeApi-go\fakeTimeApi_test.go:169
            Error:          Not equal:
                            expected: 0
                            actual  : 21
            Test:           TestFakeApi

Injecting your own events

  • the fake time allows you to inject your own events into the time fakes event log
  • this will greatly help you track down what ran when after more complicated tests with many threads
fakeTimeApi, ok := timeapi.(*FakeTimeApi)
if ok {
    fakeTimeApi.AddEvent("    My Thingy Ran!")
}

Sometimes the defaults just aint right

  • the fake checks that your channels are drained and such, passing the the testing context b or t, will give you more info if it detects something and panics
  • cloud build systems under heavy load can take forever to allow bg threads to all run during tests, sometimes you need to wait longer
  • conversely if you have a test that is slow due to all the background sleeping maybe you want to reduce flush time, or eliminate it
timeapi := timeApi.NewFake().
    SetOptions(timeApi.FakeOptions().WithTesting(t).WithFlushTime(4 * time.Millisecond)).
    Start(time.Date(2009, 11, 17, 20, 34, 58, 0, time.UTC))

Monitoring your timer state

  • internally the time api stores anything that "ticks" as a TickProducer.
timeapi := timeApi.NewFake().Start(time.Now())

assert.Equal(t, 0, timeapi.TickProducerCount())

events := timeapi.AppendEvents(make([]string, 0, 1024))
assert.Equal(t, 0, len(events))

tickProducerNames := timeapi.AppendTickProducerNames(make([]string, 0, 1024))
assert.Equal(t, 0, len(tickProducerNames))

Using the timerProvider convenience wrapper

Using the timerProvider

  • this providers an easy way to start/stop a bg thread that calls a worker function, with race safety etc.
  • it also injects its update calls into the fake timer event log
// init time Api
timeapi := timeApi.New()

// start my timer
timerProvider, _ := NewTimerProvider(timeapi)
var runCount int
const CHECK_INTERVAL = 100 * time.Millisecond
timer := timerProvider.SetInterval(func() { runCount += 1 }, CHECK_INTERVAL)

// this stops the timer cleanly, leaking nothing, plus has a lock internally, so you can assert anything it touched safely in tests
// like the above runCount variable.
timer.Stop()

You can also monitor the timer for how many times its ticked, or wait until its done the tick count you desire this can be helpful in tests:

timeapi := timeApi.NewFake().
    SetOptions(timeApi.FakeOptions().WithTesting(t).WithFlushTime(0)).
    Start(time.Date(2009, 11, 17, 20, 34, 58, 0, time.UTC))

timerProvider, _ := NewTimerProvider(timeapi)
var runCount int
const CHECK_INTERVAL = 100 * time.Millisecond
timer := timerProvider.SetInterval(func() { runCount += 1 }, CHECK_INTERVAL)
timeapi.IncrementClock(CHECK_INTERVAL)
timer.WaitUntilCount(1)

assert.Equal(t, timer.Count(), runCount)
timeapi.Stop()
assert.Equal(t, 1, runCount)

Benchmark

  • Not really relevant to this module

Contact

Ryan Haksi [ryan.haksi@gmail.com]

License

Available under the MIT License.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var MaxDuration = 1<<63 - 1

UnixNano only works within ±292 years around 1970 (between 1678 and 2262). Also, since maximum duration is ~292 years, even those two will give a clamped result on Sub

View Source
var MaxTime = MinTime.Add(1<<63 - 1)
View Source
var MinTime = time.Unix(-2208988800, 0) // Jan 1, 1900

Functions

func AssertEventCount

func AssertEventCount(tb testing.TB, timeApi *FakeTimeApi, count int)

assert the time event log is the size you expect, else spam the test output with the event log.

func ContextName

func ContextName(c context.Context) string

helper function, get the name of a context via the public String() method thats not in the interface

func DurationString

func DurationString(d time.Duration) string

convert time durations into something easier to read 2h, 2m, 2s, 2ms, 2us, or 2ns for example

func FakeOptions

func FakeOptions() fakeOptions

Types

type FakeTicker

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

fake time api implementation of ticker

func (*FakeTicker) Reset

func (t *FakeTicker) Reset(d time.Duration)

Reset stops a ticker and resets its period to the specified duration. The next tick will arrive after the new period elapses. The duration d must be greater than zero; if not, Reset will panic.

func (*FakeTicker) Stop

func (t *FakeTicker) Stop()

Stop turns off a ticker. After Stop, no more ticks will be sent. Stop does not close the channel, to prevent a concurrent goroutine reading from the channel from seeing an erroneous "tick".

type FakeTimeApi

type FakeTimeApi struct {
	// contains filtered or unexported fields
}

the fake time api struct

func NewFake

func NewFake(opts ...fakeOptions) *FakeTimeApi

NewFake returns test fake so your tests can control the when and how much time advances

func WithFakeTime

func WithFakeTime(startTime time.Time, fn func(timeApi *FakeTimeApi)) *FakeTimeApi

convenience wrapper to generate a fake and start it

func (*FakeTimeApi) AddEvent

func (t *FakeTimeApi) AddEvent(event string)

add a user event to the fakes time event log Use like:

 fakeTimeApi, ok := timeapi.(*FakeTimeApi)
	if ok {
	    fakeTimeApi.AddEvent("    My Thingy Ran!")
	}

func (*FakeTimeApi) After

func (t *FakeTimeApi) After(d time.Duration) <-chan time.Time

func (*FakeTimeApi) AfterFunc

func (t *FakeTimeApi) AfterFunc(d time.Duration, f func()) *Timer

The real AfterFunc waits for the duration to elapse and then calls f in its own goroutine. It returns a Timer that can be used to cancel the call using its Stop method. This version just calls it from whichever goroutine is running the AdvanceClock operation

func (*FakeTimeApi) AppendEvents

func (t *FakeTimeApi) AppendEvents(events []string) []string

a copy of the timeapi's event log

func (*FakeTimeApi) AppendTickProducerNames

func (t *FakeTimeApi) AppendTickProducerNames(names []string) []string

Get list of names for active tick producers Inactive tick producers are only reaped whenever you advance or stop the clock

func (*FakeTimeApi) Gosched

func (t *FakeTimeApi) Gosched()

gosched typically takes about 45-60ns to run on my machine, but sometimes 0ns presumably depending on core task queue size as a guess, the fake advances the clock 60ns when you call its fake version. This does not cause a sleep, only a call to gosched

func (*FakeTimeApi) IncrementClock

func (t *FakeTimeApi) IncrementClock(d time.Duration) *FakeTimeApi

Make time pass

func (*FakeTimeApi) NewTicker

func (t *FakeTimeApi) NewTicker(d time.Duration) *Ticker

func (*FakeTimeApi) NewTimer

func (t *FakeTimeApi) NewTimer(d time.Duration) *Timer

func (*FakeTimeApi) Now

func (t *FakeTimeApi) Now() time.Time

func (*FakeTimeApi) SetOptions

func (t *FakeTimeApi) SetOptions(opts fakeOptions) *FakeTimeApi

if you reuse your fake across many tests, sometimses you need to change the options

func (*FakeTimeApi) Since

func (t *FakeTimeApi) Since(tm time.Time) time.Duration

func (*FakeTimeApi) Sleep

func (t *FakeTimeApi) Sleep(d time.Duration)

WARNING! If you sleep concurrently the clock will advance non-deterministically, in the event you sleep less than 2ms, the clock will advance 2ms to match real world sleep behavior on windows (ie you sleep at least the requested amount, bounded by clock granularity)

func (*FakeTimeApi) Start

func (t *FakeTimeApi) Start(tm time.Time) *FakeTimeApi

uses start/stop pattern so it can track resource leaks

func (*FakeTimeApi) Stop

func (t *FakeTimeApi) Stop() *FakeTimeApi

stops the timer,

func (*FakeTimeApi) Tick

func (t *FakeTimeApi) Tick(d time.Duration) <-chan time.Time

func (*FakeTimeApi) TickProducerCount

func (t *FakeTimeApi) TickProducerCount() int

Get the count of active tick producers. Inactive tick producers are only reaped whenever you advance or stop the clock

func (*FakeTimeApi) Until

func (t *FakeTimeApi) Until(tm time.Time) time.Duration

func (*FakeTimeApi) WithDeadline

func (t *FakeTimeApi) WithDeadline(ctx context.Context, tm time.Time) (context.Context, context.CancelFunc)

func (*FakeTimeApi) WithTimeout

type FakeTimer

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

fake time api implementation of timer

func (*FakeTimer) Reset

func (t *FakeTimer) Reset(d time.Duration) bool

Reset changes the timer to expire after duration d. It returns true if the timer had been active, false if the timer had expired or been stopped.

For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.

If a program has already received a value from t.C, the timer is known to have expired and the channel drained, so t.Reset can be used directly. If a program has not yet received a value from t.C, however, the timer must be stopped and—if Stop reports that the timer expired before being stopped—the channel explicitly drained:

if !t.Stop() {
    <-t.C
}
t.Reset(d)

This should not be done concurrent to other receives from the Timer's channel.

Note that it is not possible to use Reset's return value correctly, as there is a race condition between draining the channel and the new timer expiring. Reset should always be invoked on stopped or expired channels, as described above. The return value exists to preserve compatibility with existing programs.

For a Timer created with AfterFunc(d, f), Reset either reschedules when f will run, in which case Reset returns true, or schedules f to run again, in which case it returns false. When Reset returns false, Reset neither waits for the prior f to complete before returning nor does it guarantee that the subsequent goroutine running f does not run concurrently with the prior one. If the caller needs to know whether the prior execution of f is completed, it must coordinate with f explicitly.

func (*FakeTimer) Stop

func (t *FakeTimer) Stop() bool

Stop prevents the Timer from firing. It returns true if the call stops the timer, false if the timer has already expired or been stopped. Stop does not close the channel, to prevent a read from the channel succeeding incorrectly.

To ensure the channel is empty after a call to Stop, check the return value and drain the channel. For example, assuming the program has not received from t.C already:

if !t.Stop() {
	<-t.C
}

This cannot be done concurrent to other receives from the Timer's channel or other calls to the Timer's Stop method.

For a timer created with AfterFunc(d, f), if t.Stop returns false, then the timer has already expired and the function f has been started in its own goroutine; Stop does not wait for f to complete before returning. If the caller needs to know whether f is completed, it must coordinate with f explicitly.

type IntervalFuncTicker

type IntervalFuncTicker struct {
	// contains filtered or unexported fields
}

IntervalFuncTicker, should probably be called a TickerFunc

func (*IntervalFuncTicker) ClearInterval

func (t *IntervalFuncTicker) ClearInterval() *IntervalFuncTicker

Stop the ticker func from executing in a race safe way.

func (*IntervalFuncTicker) Count

func (t *IntervalFuncTicker) Count() int

get the number of times the callback function has been invoked

func (*IntervalFuncTicker) WaitUntilCount

func (t *IntervalFuncTicker) WaitUntilCount(count int) (sleepCount int)

spin with timeapi.Sleep until the invocation count is as specified returns the # of 1ms sleeps it waited.

type RealTimeApi

type RealTimeApi struct {
	// contains filtered or unexported fields
}

The real time api struct, wraps the system time/context/goshed functions

func New

func New() *RealTimeApi

New returns an instance of a real-time clock. This is a simple wrapper around the time.* functions in the go standard library

func (*RealTimeApi) After

func (t *RealTimeApi) After(d time.Duration) <-chan time.Time

After waits for the duration to elapse and then sends the current time on the returned channel. It is equivalent to NewTimer(d).C. The underlying Timer is not recovered by the garbage collector until the timer fires. If efficiency is a concern, use NewTimer instead and call Timer.Stop if the timer is no longer needed.

func (*RealTimeApi) AfterFunc

func (t *RealTimeApi) AfterFunc(d time.Duration, f func()) *Timer

AfterFunc waits for the duration to elapse and then calls f in its own goroutine. It returns a Timer that can be used to cancel the call using its Stop method.

func (*RealTimeApi) Gosched

func (t *RealTimeApi) Gosched()

Gosched yields the processor, allowing other goroutines to run. It does not suspend the current goroutine, so execution resumes automatically. Note gosched typically takes about 45-60ns to run on my machine

func (*RealTimeApi) NewTicker

func (t *RealTimeApi) NewTicker(d time.Duration) *Ticker

NewTicker returns a new Ticker containing a channel that will send the current time on the channel after each tick. The period of the ticks is specified by the duration argument. The ticker will adjust the time interval or drop ticks to make up for slow receivers. The duration d must be greater than zero; if not, NewTicker will panic. Stop the ticker to release associated resources.

func (*RealTimeApi) NewTimer

func (t *RealTimeApi) NewTimer(d time.Duration) *Timer

NewTimer creates a new Timer that will send the current time on its channel after at least duration d.

func (*RealTimeApi) Now

func (t *RealTimeApi) Now() time.Time

Now returns the current local time.

func (*RealTimeApi) Since

func (t *RealTimeApi) Since(tm time.Time) time.Duration

Since returns the time elapsed since t. It is shorthand for time.Now().Sub(t).

func (*RealTimeApi) Sleep

func (t *RealTimeApi) Sleep(d time.Duration)

Sleep pauses the current goroutine for at least the duration d. A negative or zero duration causes Sleep to return immediately. Note on windows golang maximum sleep resolution is roughly 2ms, any amount < than that will sleep for that long

func (*RealTimeApi) Tick

func (t *RealTimeApi) Tick(d time.Duration) <-chan time.Time

Tick is a convenience wrapper for NewTicker providing access to the ticking channel only. While Tick is useful for clients that have no need to shut down the Ticker, be aware that without a way to shut it down the underlying Ticker cannot be recovered by the garbage collector; it "leaks". Unlike NewTicker, Tick will return nil if d <= 0. Wise programmers should avoid this abomination like the plague

func (*RealTimeApi) Until

func (t *RealTimeApi) Until(tm time.Time) time.Duration

Until returns the duration until t. It is shorthand for t.Sub(time.Now()).

func (*RealTimeApi) WithDeadline

func (t *RealTimeApi) WithDeadline(ctx context.Context, tm time.Time) (context.Context, context.CancelFunc)

WithDeadline returns a copy of the parent context with the deadline adjusted to be no later than d. If the parent's deadline is already earlier than d, WithDeadline(parent, d) is semantically equivalent to parent. The returned context's Done channel is closed when the deadline expires, when the returned cancel function is called, or when the parent context's Done channel is closed, whichever happens first.

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

func (*RealTimeApi) WithTimeout

WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete:

func slowOperationWithTimeout(ctx context.Context) (Result, error) {
	ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
	defer cancel()  // releases resources if slowOperation completes before timeout elapses
	return slowOperation(ctx)
}

type Ticker

type Ticker struct {
	C <-chan time.Time // republished channel from the internal adapter
	// contains filtered or unexported fields
}

Cant put a channel datamember in an interface, so we need an adapter to wrap and make the channel accessible has the comments from time.Ticker on its methods so your intellesence looks the same

func (*Ticker) Reset

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

Reset stops a ticker and resets its period to the specified duration. The next tick will arrive after the new period elapses. The duration d must be greater than zero; if not, Reset will panic.

func (*Ticker) Stop

func (t *Ticker) Stop()

Stop turns off a ticker. After Stop, no more ticks will be sent. Stop does not close the channel, to prevent a concurrent goroutine reading from the channel from seeing an erroneous "tick".

type TickerAdapter

type TickerAdapter interface {
	Stop()
	Reset(d time.Duration)
}

the interface to a ticker, real or fake

type TimeApi

type TimeApi interface {
	After(d time.Duration) <-chan time.Time
	AfterFunc(d time.Duration, f func()) *Timer
	Now() time.Time
	Since(t time.Time) time.Duration
	Until(t time.Time) time.Duration
	Sleep(d time.Duration)
	Gosched()
	Tick(d time.Duration) <-chan time.Time
	NewTicker(d time.Duration) *Ticker
	NewTimer(d time.Duration) *Timer
	WithDeadline(parent context.Context, d time.Time) (context.Context, context.CancelFunc)
	WithTimeout(parent context.Context, t time.Duration) (context.Context, context.CancelFunc)
}

Inspired by github.com/benbjohnson/clock wrap all the global time methods, plus gosched, and the context timeouts

type Timer

type Timer struct {
	C <-chan time.Time // republished channel from the internal adapter
	// contains filtered or unexported fields
}

Cant put a channel datamember in an interface, so we need an adapter to wrap and make the channel accessible has the comments from time.Timer on its methods so your intellesence looks the same

func (*Timer) Reset

func (t *Timer) Reset(d time.Duration) bool

Reset should be invoked only on stopped or expired timers with drained channels.

If a program has already received a value from t.C, the timer is known to have expired and the channel drained, so t.Reset can be used directly. If a program has not yet received a value from t.C, however, the timer must be stopped and—if Stop reports that the timer expired before being stopped—the channel explicitly drained:

if !t.Stop() {
	<-t.C
}
t.Reset(d)

func (*Timer) Stop

func (t *Timer) Stop() bool

Stop prevents the Timer from firing. It returns true if the call stops the timer, false if the timer has already expired or been stopped. Stop does not close the channel, to prevent a read from the channel succeeding incorrectly.

To ensure the channel is empty after a call to Stop, check the return value and drain the channel. For example, assuming the program has not received from t.C already:

if !t.Stop() {
	<-t.C
}

This cannot be done concurrent to other receives from the Timer's channel or other calls to the Timer's Stop method.

For a timer created with AfterFunc(d, f), if t.Stop returns false, then the timer has already expired and the function f has been started in its own goroutine; Stop does not wait for f to complete before returning. If the caller needs to know whether f is completed, it must coordinate with f explicitly.

type TimerAdapter

type TimerAdapter interface {
	Stop() bool
	Reset(d time.Duration) bool
}

the interface to a timer, real or fake

type TimerProvider

type TimerProvider struct {
	// contains filtered or unexported fields
}

Convenience wrapper for making tickers. Hmm, should probably have called it a TickerFuncProvider

func NewTimerProvider

func NewTimerProvider(timeApi TimeApi) (*TimerProvider, error)

func (*TimerProvider) SetInterval

func (t *TimerProvider) SetInterval(fn func(tm time.Time), interval time.Duration) *IntervalFuncTicker

creates a ticker, and applies a mutex so that the func(tm time.Time) you pass will be invoked every heart beat it can only run one at time (it will serialize around its internal mutex) stopping it is likewise safe via mutex

Jump to

Keyboard shortcuts

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