Documentation
¶
Overview ¶
Package keylock is a set of mutexes, one per key, for work that must not interleave with itself on one key while other keys proceed. A wait ends when its context does, and the caller then holds nothing. A key's entry is reference-counted and lives only while a caller holds or waits on it, so memory follows in-flight work, not the key space. Set.LockAll takes several keys in the one order the Set was built with, so callers naming the same keys in any order never deadlock.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Set ¶
type Set[K comparable] struct { // contains filtered or unexported fields }
Set holds one lock per key and is safe for concurrent use. Construct it with New; the zero value is not usable, and a Set must not be copied after first use. Lock and LockAll panic, holding nothing, on a key that is not equal to itself: a floating-point NaN, or an interface value whose dynamic type is not comparable.
func New ¶
func New[K comparable](cmp func(a, b K) int) *Set[K]
New returns an empty Set whose Set.LockAll orders keys by cmp. cmp must be a strict total order consistent with ==: cmp(a, b) == 0 exactly when a == b. New panics on a nil cmp.
func (*Set[K]) Lock ¶
Lock blocks until k is free or ctx is done. On success the caller holds k until it calls unlock, which is safe to call more than once. On failure it returns ctx.Err() and holds nothing; a ctx already done never takes k, even a free one. It panics, holding nothing, on a k not equal to itself (see Set).
Example ¶
package main
import (
"cmp"
"context"
"fmt"
"time"
"github.com/cplieger/keylock"
)
func main() {
users := keylock.New(cmp.Compare[int])
unlock, err := users.Lock(context.Background(), 42)
if err != nil {
fmt.Println(err)
return
}
defer unlock()
// A second caller on the same user waits until its context ends, then
// holds nothing.
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
_, err = users.Lock(ctx, 42)
fmt.Println(err)
}
Output: context deadline exceeded
func (*Set[K]) LockAll ¶
LockAll takes every key in keys or none of them. It takes each distinct key once, in the Set's order, so callers naming the same keys in any order never deadlock, and it leaves keys unmodified. On failure it returns ctx.Err() with every key it took released. unlock releases them all and is safe to call more than once; with no keys there is nothing to take and LockAll succeeds. LockAll panics when the order calls two distinct keys equal, because it could not order them the same way for every caller, and on any key not equal to itself (see Set); it checks every key before it takes one, so either panic leaves nothing held.
Example ¶
package main
import (
"context"
"fmt"
"strings"
"sync"
"github.com/cplieger/keylock"
)
func main() {
files := keylock.New(strings.Compare)
ctx := context.Background()
// Each writer names the same two files in its own order. LockAll takes
// them in the Set's order, so neither writer can hold one file while
// waiting on the other's.
var wg sync.WaitGroup
for _, names := range [][]string{{"movie.en.srt", "movie.fr.srt"}, {"movie.fr.srt", "movie.en.srt"}} {
wg.Go(func() {
unlock, err := files.LockAll(ctx, names)
if err != nil {
fmt.Println(err)
return
}
defer unlock()
})
}
wg.Wait()
fmt.Println("both writers finished")
}
Output: both writers finished