Documentation ¶
Index ¶
- type Constructor
- type Destructor
- type UsagePool
- func (up *UsagePool) Delete(key any) (deleted bool, err error)
- func (up *UsagePool) LoadOrNew(key any, construct Constructor) (value any, loaded bool, err error)
- func (up *UsagePool) LoadOrStore(key, val any) (value any, loaded bool)
- func (up *UsagePool) Range(f func(key, value any) bool)
- func (up *UsagePool) References(key any) (int, bool)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Constructor ¶
type Constructor func() (Destructor, error)
Constructor is a function that returns a new value that can destruct itself when it is no longer needed.
type Destructor ¶
type Destructor interface {
Destruct() error
}
Destructor is a value that can clean itself up when it is deallocated.
type UsagePool ¶
UsagePool is a thread-safe map that pools values based on usage (reference counting). Values are only inserted if they do not already exist. There are two ways to add values to the pool:
- LoadOrStore will increment usage and store the value immediately if it does not already exist.
- LoadOrNew will atomically check for existence and construct the value immediately if it does not already exist, or increment the usage otherwise, then store that value in the pool. When the constructed value is finally deleted from the pool (when its usage reaches 0), it will be cleaned up by calling Destruct().
The use of LoadOrNew allows values to be created and reused and finally cleaned up only once, even though they may have many references throughout their lifespan. This is helpful, for example, when sharing thread-safe io.Writers that you only want to open and close once.
There is no way to overwrite existing keys in the pool without first deleting it as many times as it was stored. Deleting too many times will panic.
The implementation does not use a sync.Pool because UsagePool needs additional atomicity to run the constructor functions when creating a new value when LoadOrNew is used. (We could probably use sync.Pool but we'd still have to layer our own additional locks on top.)
An empty UsagePool is NOT safe to use; always call NewUsagePool() to make a new one.
func NewUsagePool ¶
func NewUsagePool() *UsagePool
NewUsagePool returns a new usage pool that is ready to use.
func (*UsagePool) Delete ¶
Delete decrements the usage count for key and removes the value from the underlying map if the usage is 0. It returns true if the usage count reached 0 and the value was deleted. It panics if the usage count drops below 0; always call Delete precisely as many times as LoadOrStore.
func (*UsagePool) LoadOrNew ¶
LoadOrNew loads the value associated with key from the pool if it already exists. If the key doesn't exist, it will call construct to create a new value and then stores that in the pool. An error is only returned if the constructor returns an error. The loaded or constructed value is returned. The loaded return value is true if the value already existed and was loaded, or false if it was newly constructed.
func (*UsagePool) LoadOrStore ¶
LoadOrStore loads the value associated with key from the pool if it already exists, or stores it if it does not exist. It returns the value that was either loaded or stored, and true if the value already existed and was
func (*UsagePool) Range ¶
Range iterates the pool similarly to how sync.Map.Range() does: it calls f for every key in the pool, and if f returns false, iteration is stopped. Ranging does not affect usage counts.
This method is somewhat naive and acquires a read lock on the entire pool during iteration, so do your best to make f() really fast, m'kay?