add git.iim.gay/grufwub/go-store for storage backend, replacing blob.Storage

Signed-off-by: kim (grufwub) <grufwub@gmail.com>
This commit is contained in:
kim (grufwub) 2021-09-11 20:12:47 +01:00
commit e43a46e982
89 changed files with 9372 additions and 240 deletions

View file

@ -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

View file

@ -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()
}

View file

@ -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{}{}
}

View file

@ -0,0 +1,10 @@
package nowish
//nolint
type noCopy struct{}
//nolint
func (*noCopy) Lock() {}
//nolint
func (*noCopy) Unlock() {}