| // Copyright 2020-2025 Buf Technologies, Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Package refcount provides utilities for working with reference-counted objects. |
| // |
| // Why would you need refcounting in a language that already has GC? The GC |
| // can't always tell that all references to some object are gone. For example, |
| // suppose that we have a map[string]*T for looking up values based on some |
| // string key, but we want to evict elements of that map if no structure holds |
| // the key to it anymore. Doing this correctly requires a separately managed |
| // refcount. For this, you would use [refcount.Map]. |
| package refcount |
| |
| import "sync" |
| |
| // Map is a map from keys of type K to values of type *V. |
| // |
| // Unlike a built-in map, refcount.Map allows for a key to be inserted multiple |
| // times concurrently, and deleted multiple times. A key that is inserted n times |
| // will only be evicted from the map once it is deleted n times. |
| // |
| // A zero map is empty and ready to use. Like other Go concurrency primitives, it |
| // must not be copied after first use. |
| // |
| // recount.Map is thread-safe: insertions synchronize-before deletions. |
| type Map[K comparable, V any] struct { |
| lock sync.RWMutex |
| table map[K]*counted[V] |
| } |
| |
| // Insert inserts a key into the map. |
| // |
| // If the value is already present in the map, its count is incremented by one; |
| // otherwise, the zero value is inserted and returned. This function returns whether |
| // an existing entry was found. |
| // |
| // The returned pointer is never nil. |
| func (m *Map[K, V]) Insert(key K) (value *V, found bool) { |
| // NOTE: By replacing counted[V].count with an atomic.Int64, this |
| // can be downgraded to a read lock, with an upgrade only in the case |
| // we are inserting a new entry. |
| // |
| // This optimization is not performed in the name of expediency, I have |
| // only recorded it as potential future work |
| m.lock.Lock() |
| defer m.lock.Unlock() |
| |
| if m.table == nil { |
| m.table = make(map[K]*counted[V]) |
| } |
| |
| v, found := m.table[key] |
| if !found { |
| v = &counted[V]{} |
| m.table[key] = v |
| } |
| v.count++ |
| return &v.value, found |
| } |
| |
| // Get looks up a key in the map. |
| // |
| // This is identical to ordinary map lookup: if they key is not present, it does not |
| // insert and returns nil. |
| func (m *Map[K, V]) Get(key K) *V { |
| m.lock.RLock() |
| defer m.lock.RUnlock() |
| value := m.table[key] |
| if value == nil { |
| return nil |
| } |
| return &value.value |
| } |
| |
| // Range ranges over this map. Beware! While ranging, writes to the map will block. |
| // |
| // This implements [iter.Seq2] for Map[K, V]. |
| func (m *Map[K, V]) Range(yield func(k K, v *V) bool) { |
| m.lock.RLock() |
| defer m.lock.RUnlock() |
| |
| for k, v := range m.table { |
| if !yield(k, &v.value) { |
| break |
| } |
| } |
| } |
| |
| // Delete deletes a key from the map. |
| // |
| // The key will only be evicted once [Map.Delete] has been called an equal number of times |
| // to prior calls to [Map.Insert] for this key. |
| // |
| // If the key is present and was actually evicted, the element it maps to is returned. Otherwise, |
| // this function returns nil. |
| func (m *Map[K, V]) Delete(key K) *V { |
| m.lock.Lock() |
| defer m.lock.Unlock() |
| |
| v := m.table[key] |
| if v == nil { |
| return nil |
| } |
| |
| v.count-- |
| if v.count > 0 { |
| return nil |
| } |
| |
| delete(m.table, key) |
| return &v.value |
| } |
| |
| // counted is a reference-counted value. |
| type counted[T any] struct { |
| count int32 // Protected by Map.lock. |
| value T |
| } |