tenant
Keeps one open value per key, usually a SQLite database per account.
Import path github.com/zweiundeins/lith/tenant. Generated from the doc comments in tenant/.
A Pool opens a value on first use and keeps it while anyone holds a pin on it. Idle values are closed, least recently released first, when a cold open would put more than maxOpen values open. The cap is soft: Get never waits for a free slot, so pinned values can exceed it.
Opens and closes run outside the pool's lock. A key that is being opened or closed is busy, and everyone else who needs that key waits for it, so a slow open (a restore from a replica) holds up only its own key and a key is never open twice.
Variables
var (
// ErrClosed is returned by Get after Close.
ErrClosed = errors.New("tenant: pool closed")
// ErrDenied is the reason Get gives for a key denied without one.
ErrDenied = errors.New("tenant: key denied")
)
Types
type Opener
type Opener[T io.Closer] func(ctx context.Context, key string) (T, error)
Opener opens the value for key. Its context carries the values of the Get that needed it but not its cancellation: a request that gives up must not abort a restore halfway.
type Option
type Option func(*options)
Option configures a Pool.
func WithLogger
func WithLogger(l *slog.Logger) Option
WithLogger sets where failed closes of evicted values are logged; the default is slog.Default().
type Pool
type Pool[T io.Closer] struct {
// contains filtered or unexported fields
}
Pool is a set of open values keyed by string. It is safe for concurrent use.
func New
func New[T io.Closer](maxOpen int, open Opener[T], opts ...Option) *Pool[T]
New returns a pool that opens values with open and closes idle ones beyond maxOpen, which must be at least 1.
func (*Pool[T]) Allow
func (p *Pool[T]) Allow(key string)
Allow lifts a Deny.
func (*Pool[T]) Close
func (p *Pool[T]) Close() error
Close waits for opens and closes in flight, then closes every value, pinned or not, and makes later Gets fail with ErrClosed. It returns the values' close errors.
func (*Pool[T]) Deny
func (p *Pool[T]) Deny(key string, reason error)
Deny makes Get fail with reason (ErrDenied if nil) for key until Allow. An open in flight is discarded when it finishes; a value already open stays open until Evict. Deny, then Evict, leaves the key closed for good, as an erasure or a restore needs.
func (*Pool[T]) Do
func (p *Pool[T]) Do(ctx context.Context, key string, fn func(T) error) error
Do runs fn with the value for key pinned for the length of the call.
func (*Pool[T]) Evict
func (p *Pool[T]) Evict(ctx context.Context, key string) error
Evict closes the value for key now, pinned or not, after any open or close of key in flight; ctx bounds only that wait. Pins held on the closed value go stale: releasing them does nothing. Evicting a key that is not open is a no-op.
func (*Pool[T]) Get
func (p *Pool[T]) Get(ctx context.Context, key string) (val T, release func(), err error)
Get returns the value for key, opening it on first use, and pins it until release is called. release may be called more than once.
Hold a pin for one unit of work, a request or a render. An SSE stream that pins its tenant for its whole life keeps it from ever being evicted and pushes the pool past maxOpen: release between renders and re-acquire, or use Do for each render.
ctx bounds the wait for another open or close of key; a cold open started by this call runs to completion. Gets that waited on an open that failed return its error rather than each trying again.
func (*Pool[T]) Stats
func (p *Pool[T]) Stats() Stats
Stats reports the pool's occupancy.
type Stats
type Stats struct {
Open int // values open
Pinned int // open values with at least one pin
Opening int // opens in flight
MaxOpen int // the soft cap
Opens uint64 // successful opens since New
Evictions uint64 // idle values closed to stay within MaxOpen
}
Stats is a snapshot of a pool.
Pinned close to MaxOpen is the condition to alert on: nothing is left to evict, and every cold open grows the pool past its cap.