pkg/lazy/lazy.go (52 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Package lazy is a package to expose lazily computed values.
package lazy
import (
"sync"
"sync/atomic"
)
type lazyImpl[T any] struct {
getter func() (T, error)
// retry, if true, will ensure getter() is called for each Get() until a non-nil error is returned.
retry bool
// Cached responses. Note: with retry enabled, this will be unset until a non-nil error
res T
err error
done uint32
m sync.Mutex
}
// Lazy represents a value whose computation is deferred until the first access.
type Lazy[T any] interface {
Get() (T, error)
}
var _ Lazy[any] = &lazyImpl[any]{}
func New[T any](f func() (T, error)) Lazy[T] {
return &lazyImpl[T]{
getter: f,
}
}
// NewWithRetry returns a new lazily computed value. The value will be computed on each call until a
// non-nil error is returned.
func NewWithRetry[T any](f func() (T, error)) Lazy[T] {
return &lazyImpl[T]{
getter: f,
retry: true,
}
}
func (l *lazyImpl[T]) Get() (T, error) {
if atomic.LoadUint32(&l.done) == 0 {
return l.doSlow()
}
return l.res, l.err
}
func (l *lazyImpl[T]) doSlow() (T, error) {
l.m.Lock()
defer l.m.Unlock()
if l.done == 0 {
done := uint32(1)
// Defer in case of panic
defer func() {
atomic.StoreUint32(&l.done, done)
}()
res, err := l.getter()
if l.retry && err != nil {
done = 0
} else {
l.res, l.err = res, err
}
return res, err
}
return l.res, l.err
}