A Go library for coalescing duplicate function calls. When multiple goroutines request the same key simultaneously, only one executes the function while others wait and receive the same result.
Also provides batching capabilities to collect multiple values and process them together.
go get github.com/KarpelesLab/unisonpackage main
import (
"fmt"
"github.com/KarpelesLab/unison"
)
var group unison.Group[string, string]
func main() {
result, err := group.Do("my-key", func() (string, error) {
// This function only runs once per key at a time
return "computed value", nil
})
if err != nil {
panic(err)
}
fmt.Println(result)
}- Multiple concurrent calls with the same key will only execute the function once
- All callers receive the same result (value and error)
- Once the function completes and results are returned, subsequent calls will trigger a new execution
- Different keys execute independently and in parallel
var userLoader unison.Group[string, *User]
func GetUser(id string) (*User, error) {
return userLoader.Do(id, func() (*User, error) {
// Even if 100 goroutines call GetUser("123") simultaneously,
// only one database query is made
return db.QueryUser(id)
})
}var cacheRefresh unison.Group[string, []Product]
func GetProducts() ([]Product, error) {
return cacheRefresh.Do("products", func() ([]Product, error) {
// When cache expires and many requests hit at once,
// only one request fetches from the source
return fetchProductsFromAPI()
})
}var configLoader unison.Group[string, *Config]
func GetConfig() (*Config, error) {
return configLoader.DoUntil("config", 5*time.Minute, func() (*Config, error) {
// Result is cached for 5 minutes
// Subsequent calls within that window return the cached result
return loadConfigFromFile()
})
}A generic type that manages in-flight function calls. K is the key type (must be comparable), T is the result type. Zero-value is ready to use.
var g unison.Group[string, string] // string keys, string values
var g unison.Group[int, *User] // int keys, *User valuesExecutes fn for the given key, ensuring only one execution is in-flight at a time per key. Concurrent callers with the same key wait for the first caller's result. Once complete, subsequent calls trigger a new execution.
Like Do, but caches the result for the specified duration. Subsequent calls within the cache window return the cached result without executing fn again. After expiration, the next call triggers a new execution.
Removes a key from the group, causing future calls to execute the function again even if a previous call is still in-flight. Goroutines already waiting for the result will still receive it. Also useful for invalidating cached results from DoUntil.
Returns the number of entries currently in the group, including both in-flight calls and cached results.
Reports whether a key exists in the group with a valid (non-expired) entry. Returns true if the key has an in-flight call or a cached result that hasn't expired.
Removes all expired entries from the group. Useful for reclaiming memory when using DoUntil with many unique keys.
Batch collects multiple calls and processes them together. When the first call arrives, execution starts immediately. Any calls that arrive while execution is in progress are collected and processed together in the next batch.
var inserter = unison.NewBatch(func(users []User) error {
// All users collected during the batch window are inserted together
return db.BulkInsert(users)
})
func CreateUser(u User) error {
// Multiple goroutines calling this will have their users batched
return inserter.Do(u)
}Creates a new Batch with the given processing function. The function will be called with batches of values as they are collected.
Adds a value to be processed and waits for the batch to complete. All callers in the same batch receive the same error result. If MaxSize is set and the pending batch is full, the caller blocks until there is room.
Maximum number of values in a pending batch. When set to a non-zero value, callers will block if the pending batch is full. Default is 0 (unlimited).
b := unison.NewBatch(func(ids []int) error {
return db.BulkInsert(ids)
})
b.MaxSize = 100 // Limit batches to 100 valuesSee LICENSE file.