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() } }