diff options
Diffstat (limited to 'vendor/git.iim.gay/grufwub/go-nowish')
-rw-r--r-- | vendor/git.iim.gay/grufwub/go-nowish/README.md | 5 | ||||
-rw-r--r-- | vendor/git.iim.gay/grufwub/go-nowish/time.go | 141 | ||||
-rw-r--r-- | vendor/git.iim.gay/grufwub/go-nowish/timeout.go | 111 | ||||
-rw-r--r-- | vendor/git.iim.gay/grufwub/go-nowish/util.go | 10 |
4 files changed, 267 insertions, 0 deletions
diff --git a/vendor/git.iim.gay/grufwub/go-nowish/README.md b/vendor/git.iim.gay/grufwub/go-nowish/README.md new file mode 100644 index 000000000..17dd75374 --- /dev/null +++ b/vendor/git.iim.gay/grufwub/go-nowish/README.md @@ -0,0 +1,5 @@ +nowish is a very simple library for creating Go clocks that give a good (ish) +estimate of the "now" time, "ish" depending on the precision you request + +similar to fastime, but more bare bones and using unsafe pointers instead of +atomic value since we don't need to worry about type changes
\ No newline at end of file diff --git a/vendor/git.iim.gay/grufwub/go-nowish/time.go b/vendor/git.iim.gay/grufwub/go-nowish/time.go new file mode 100644 index 000000000..30027ac0b --- /dev/null +++ b/vendor/git.iim.gay/grufwub/go-nowish/time.go @@ -0,0 +1,141 @@ +package nowish + +import ( + "sync" + "sync/atomic" + "time" + "unsafe" +) + +// Start returns a new Clock instance initialized and +// started with the provided precision, along with the +// stop function for it's underlying timer +func Start(precision time.Duration) (*Clock, func()) { + c := Clock{} + return &c, c.Start(precision) +} + +type Clock struct { + noCopy noCopy //nolint noCopy because a copy will fuck with atomics + + // format stores the time formatting style string + format string + + // valid indicates whether the current value stored in .Format is valid + valid uint32 + + // mutex protects writes to .Format, not because it would be unsafe, but + // because we want to minimize unnnecessary allocations + mutex sync.Mutex + + // Format is an unsafe pointer to the last-updated time format string + Format unsafe.Pointer + + // Time is an unsafe pointer to the last-updated time.Time object + Time unsafe.Pointer +} + +// Start starts the clock with the provided precision, the +// returned function is the stop function for the underlying timer +func (c *Clock) Start(precision time.Duration) func() { + // Create ticker from duration + tick := time.NewTicker(precision) + + // Set initial time + t := time.Now() + atomic.StorePointer(&c.Time, unsafe.Pointer(&t)) + + // Set initial format + s := "" + atomic.StorePointer(&c.Format, unsafe.Pointer(&s)) + + // If formatting string unset, set default + c.mutex.Lock() + if c.format == "" { + c.format = time.RFC822 + } + c.mutex.Unlock() + + // Start main routine + go c.run(tick) + + // Return stop fn + return tick.Stop +} + +// run is the internal clock ticking loop +func (c *Clock) run(tick *time.Ticker) { + for { + // Wait on tick + _, ok := <-tick.C + + // Channel closed + if !ok { + break + } + + // Update time + t := time.Now() + atomic.StorePointer(&c.Time, unsafe.Pointer(&t)) + + // Invalidate format string + atomic.StoreUint32(&c.valid, 0) + } +} + +// Now returns a good (ish) estimate of the current 'now' time +func (c *Clock) Now() time.Time { + return *(*time.Time)(atomic.LoadPointer(&c.Time)) +} + +// NowFormat returns the formatted "now" time, cached until next tick and "now" updates +func (c *Clock) NowFormat() string { + // If format still valid, return this + if atomic.LoadUint32(&c.valid) == 1 { + return *(*string)(atomic.LoadPointer(&c.Format)) + } + + // Get mutex lock + c.mutex.Lock() + + // Double check still invalid + if atomic.LoadUint32(&c.valid) == 1 { + c.mutex.Unlock() + return *(*string)(atomic.LoadPointer(&c.Format)) + } + + // Calculate time format + b := c.Now().AppendFormat( + make([]byte, 0, len(c.format)), + c.format, + ) + + // Update the stored value and set valid! + atomic.StorePointer(&c.Format, unsafe.Pointer(&b)) + atomic.StoreUint32(&c.valid, 1) + + // Unlock and return + c.mutex.Unlock() + + // Note: + // it's safe to do this conversion here + // because this byte slice will never change. + // and we have the direct pointer to it, we're + // not requesting it atomicly via c.Format + return *(*string)(unsafe.Pointer(&b)) +} + +// SetFormat sets the time format string used by .NowFormat() +func (c *Clock) SetFormat(format string) { + // Get mutex lock + c.mutex.Lock() + + // Update time format + c.format = format + + // Invalidate current format string + atomic.StoreUint32(&c.valid, 0) + + // Unlock + c.mutex.Unlock() +} diff --git a/vendor/git.iim.gay/grufwub/go-nowish/timeout.go b/vendor/git.iim.gay/grufwub/go-nowish/timeout.go new file mode 100644 index 000000000..15222281a --- /dev/null +++ b/vendor/git.iim.gay/grufwub/go-nowish/timeout.go @@ -0,0 +1,111 @@ +package nowish + +import ( + "errors" + "sync/atomic" + "time" +) + +// ErrTimeoutStarted is returned if a Timeout interface is attempted to be reused while still in operation +var ErrTimeoutStarted = errors.New("nowish: timeout already started") + +// timeoutState provides a thread-safe timeout state mechanism +type timeoutState uint32 + +// start attempts to start the state, must be already reset, returns success +func (t *timeoutState) start() bool { + return atomic.CompareAndSwapUint32((*uint32)(t), 0, 1) +} + +// stop attempts to stop the state, must already be started, returns success +func (t *timeoutState) stop() bool { + return atomic.CompareAndSwapUint32((*uint32)(t), 1, 2) +} + +// reset is fairly self explanatory +func (t *timeoutState) reset() { + atomic.StoreUint32((*uint32)(t), 0) +} + +// Timeout provides a reusable structure for enforcing timeouts with a cancel +type Timeout interface { + // Start starts the timer with supplied timeout. If timeout is reached before + // cancel then supplied timeout hook will be called. Error may be called if + // Timeout is already running when this function is called + Start(time.Duration, func()) error + + // Cancel cancels the currently running timer. If a cancel is achieved, then + // this function will return after the timeout goroutine is finished + Cancel() +} + +// NewTimeout returns a new Timeout instance +func NewTimeout() Timeout { + t := &timeout{ + tk: time.NewTicker(time.Minute), + ch: make(chan struct{}), + } + t.tk.Stop() // don't keep it running + return t +} + +// timeout is the Timeout implementation that we force +// initialization on via NewTimeout by unexporting it +type timeout struct { + noCopy noCopy //nolint noCopy because a copy will mess with atomics + + tk *time.Ticker // tk is the underlying timeout-timer + ch chan struct{} // ch is the cancel propagation channel + st timeoutState // st stores the current timeout state (and protects concurrent use) +} + +func (t *timeout) Start(d time.Duration, hook func()) error { + // Attempt to acquire start + if !t.st.start() { + return ErrTimeoutStarted + } + + // Start the ticker + t.tk.Reset(d) + + go func() { + cancelled := false + + select { + // Timeout reached + case <-t.tk.C: + if !t.st.stop() { + // cancel was called in the nick of time + <-t.ch + cancelled = true + } + + // Cancel called + case <-t.ch: + cancelled = true + } + + // Stop ticker + t.tk.Stop() + + // If timed out call hook + if !cancelled { + hook() + } + + // Finally, reset state + t.st.reset() + }() + + return nil +} + +func (t *timeout) Cancel() { + // Attempt to acquire stop + if !t.st.stop() { + return + } + + // Send a cancel signal + t.ch <- struct{}{} +} diff --git a/vendor/git.iim.gay/grufwub/go-nowish/util.go b/vendor/git.iim.gay/grufwub/go-nowish/util.go new file mode 100644 index 000000000..31fe9050e --- /dev/null +++ b/vendor/git.iim.gay/grufwub/go-nowish/util.go @@ -0,0 +1,10 @@ +package nowish + +//nolint +type noCopy struct{} + +//nolint +func (*noCopy) Lock() {} + +//nolint +func (*noCopy) Unlock() {} |