blob: 57017d07ab7bcd030f9c1b38f1e5490726e6dd46 [file] [log] [blame]
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The resource package defines types to track the lifecycle of transient
// resources, such as a database connection, that should be renewed at some
// fixed interval.
package resource
import (
"io"
"sync"
"sync/atomic"
"time"
)
type instance[T io.Closer] struct {
refs atomic.Int64
v T
}
func (i *instance[T]) acquire(initial bool) (T, func()) {
if i.refs.Add(1) <= 1 && !initial {
panic("acquire on released instance")
}
return i.v, func() {
if i.refs.Add(-1) == 0 {
i.v.Close() // ignore error
var zero T
i.v = zero // aid GC
}
}
}
// A Resource is a container for a transient resource of type T that should be
// periodically renewed, such as a database connection.
//
// Its Get method returns an instance of the resource, along with a release
// function that the caller must invoke when it is done with the resource.
//
// The first call to Get creates a new resource instance. This instance is
// cached and returned by subsequent calls to Get for a fixed duration. After
// this duration expires, the next call to Get will create a new instance. The
// expired instance is closed once all its users have released it.
//
// A Resource is safe for concurrent use.
type Resource[T io.Closer] struct {
get func() T
validFor time.Duration
after func(func()) // for testing; fakes time.AfterFunc(validFor, f)
mu sync.Mutex
cur *instance[T]
}
// New creates a new Resource that is valid for the given duration. The get
// function is called to create a new resource instance when one is needed.
func New[T io.Closer](get func() T, validFor time.Duration) *Resource[T] {
r := newAfter(get, func(f func()) {
time.AfterFunc(validFor, f)
})
r.validFor = validFor
return r
}
// newAfter returns a new resource with a fake after func, for testing.
func newAfter[T io.Closer](get func() T, after func(func())) *Resource[T] {
return &Resource[T]{get: get, after: after}
}
// Get returns the current resource instance and a function to release it.
// The release function must be called when the caller is done with the
// resource.
func (r *Resource[T]) Get() (T, func()) {
r.mu.Lock()
defer r.mu.Unlock()
if r.cur == nil {
r.cur = &instance[T]{v: r.get()}
// Acquire one ref for the new instance that lasts the duration.
//
// This is distinct from the ref acquired below; it ensures that the
// resource is not released until the duration has expired.
_, release := r.cur.acquire(true)
r.after(func() {
r.mu.Lock()
defer r.mu.Unlock()
release()
r.cur = nil
})
}
return r.cur.acquire(false)
}