Introduces the keyed exclusion primitive that StoreSource and evictSourceBlob will hold across their full operation, so a store and an eviction racing on identical content bytes cannot interleave. Covered here in isolation: same-key exclusion, independence across distinct keys, and that the entry map does not grow unbounded.
65 lines
1.8 KiB
Go
65 lines
1.8 KiB
Go
package imgcache
|
|
|
|
import "sync"
|
|
|
|
// contentLock provides per-key mutual exclusion for content-hash keyed
|
|
// operations. StoreSource and evictSourceBlob each hold a content
|
|
// hash's lock for the full duration of their file-plus-accounting-row
|
|
// work, so a store and an eviction racing on identical content bytes
|
|
// (the real SHA-256 content-addressed dedup case, not a contrived one)
|
|
// can never interleave: the unlink of an evicted blob's file can never
|
|
// race the creation of a fresh database row for a concurrently
|
|
// re-stored copy of the same content. Entries are removed once no
|
|
// goroutine holds or is waiting for them, so a long-running process
|
|
// does not accumulate memory proportional to the number of distinct
|
|
// content hashes it has ever seen.
|
|
type contentLock struct {
|
|
mu sync.Mutex
|
|
entries map[string]*contentLockEntry
|
|
}
|
|
|
|
// contentLockEntry is one key's exclusion lock plus a count of
|
|
// goroutines currently holding or waiting to acquire it, used to know
|
|
// when it is safe to remove the entry from the map.
|
|
type contentLockEntry struct {
|
|
mu sync.Mutex
|
|
count int
|
|
}
|
|
|
|
// newContentLock creates an empty contentLock.
|
|
func newContentLock() *contentLock {
|
|
return &contentLock{entries: make(map[string]*contentLockEntry)}
|
|
}
|
|
|
|
// Lock acquires exclusive access for key, blocking until it is
|
|
// available, and returns a function that releases it. The caller must
|
|
// invoke the returned function exactly once to release the lock.
|
|
func (c *contentLock) Lock(key string) func() {
|
|
c.mu.Lock()
|
|
|
|
entry, ok := c.entries[key]
|
|
if !ok {
|
|
entry = &contentLockEntry{}
|
|
c.entries[key] = entry
|
|
}
|
|
|
|
entry.count++
|
|
|
|
c.mu.Unlock()
|
|
|
|
entry.mu.Lock()
|
|
|
|
return func() {
|
|
entry.mu.Unlock()
|
|
|
|
c.mu.Lock()
|
|
|
|
entry.count--
|
|
if entry.count == 0 {
|
|
delete(c.entries, key)
|
|
}
|
|
|
|
c.mu.Unlock()
|
|
}
|
|
}
|