| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package unique |
| |
| import ( |
| "internal/abi" |
| "internal/concurrent" |
| "internal/weak" |
| "runtime" |
| "sync" |
| "unsafe" |
| ) |
| |
| var zero uintptr |
| |
| // Handle is a globally unique identity for some value of type T. |
| // |
| // Two handles compare equal exactly if the two values used to create the handles |
| // would have also compared equal. The comparison of two handles is trivial and |
| // typically much more efficient than comparing the values used to create them. |
| type Handle[T comparable] struct { |
| value *T |
| } |
| |
| // Value returns a shallow copy of the T value that produced the Handle. |
| // Value is safe for concurrent use by multiple goroutines. |
| func (h Handle[T]) Value() T { |
| return *h.value |
| } |
| |
| // Make returns a globally unique handle for a value of type T. Handles |
| // are equal if and only if the values used to produce them are equal. |
| // Make is safe for concurrent use by multiple goroutines. |
| func Make[T comparable](value T) Handle[T] { |
| // Find the map for type T. |
| typ := abi.TypeFor[T]() |
| if typ.Size() == 0 { |
| return Handle[T]{(*T)(unsafe.Pointer(&zero))} |
| } |
| ma, ok := uniqueMaps.Load(typ) |
| if !ok { |
| // This is a good time to initialize cleanup, since we must go through |
| // this path on the first use of Make, and it's not on the hot path. |
| setupMake.Do(registerCleanup) |
| ma = addUniqueMap[T](typ) |
| } |
| m := ma.(*uniqueMap[T]) |
| |
| // Keep around any values we allocate for insertion. There |
| // are a few different ways we can race with other threads |
| // and create values that we might discard. By keeping |
| // the first one we make around, we can avoid generating |
| // more than one per racing thread. |
| var ( |
| toInsert *T // Keep this around to keep it alive. |
| toInsertWeak weak.Pointer[T] |
| ) |
| newValue := func() (T, weak.Pointer[T]) { |
| if toInsert == nil { |
| toInsert = new(T) |
| *toInsert = clone(value, &m.cloneSeq) |
| toInsertWeak = weak.Make(toInsert) |
| } |
| return *toInsert, toInsertWeak |
| } |
| var ptr *T |
| for { |
| // Check the map. |
| wp, ok := m.Load(value) |
| if !ok { |
| // Try to insert a new value into the map. |
| k, v := newValue() |
| wp, _ = m.LoadOrStore(k, v) |
| } |
| // Now that we're sure there's a value in the map, let's |
| // try to get the pointer we need out of it. |
| ptr = wp.Strong() |
| if ptr != nil { |
| break |
| } |
| // The weak pointer is nil, so the old value is truly dead. |
| // Try to remove it and start over. |
| m.CompareAndDelete(value, wp) |
| } |
| runtime.KeepAlive(toInsert) |
| return Handle[T]{ptr} |
| } |
| |
| var ( |
| // uniqueMaps is an index of type-specific concurrent maps used for unique.Make. |
| // |
| // The two-level map might seem odd at first since the HashTrieMap could have "any" |
| // as its key type, but the issue is escape analysis. We do not want to force lookups |
| // to escape the argument, and using a type-specific map allows us to avoid that where |
| // possible (for example, for strings and plain-ol'-data structs). We also get the |
| // benefit of not cramming every different type into a single map, but that's certainly |
| // not enough to outweigh the cost of two map lookups. What is worth it though, is saving |
| // on those allocations. |
| uniqueMaps = concurrent.NewHashTrieMap[*abi.Type, any]() // any is always a *uniqueMap[T]. |
| |
| // cleanupFuncs are functions that clean up dead weak pointers in type-specific |
| // maps in uniqueMaps. We express cleanup this way because there's no way to iterate |
| // over the sync.Map and call functions on the type-specific data structures otherwise. |
| // These cleanup funcs each close over one of these type-specific maps. |
| // |
| // cleanupMu protects cleanupNotify and is held across the entire cleanup. Used for testing. |
| // cleanupNotify is a test-only mechanism that allow tests to wait for the cleanup to run. |
| cleanupMu sync.Mutex |
| cleanupFuncsMu sync.Mutex |
| cleanupFuncs []func() |
| cleanupNotify []func() // One-time notifications when cleanups finish. |
| ) |
| |
| type uniqueMap[T comparable] struct { |
| *concurrent.HashTrieMap[T, weak.Pointer[T]] |
| cloneSeq |
| } |
| |
| func addUniqueMap[T comparable](typ *abi.Type) *uniqueMap[T] { |
| // Create a map for T and try to register it. We could |
| // race with someone else, but that's fine; it's one |
| // small, stray allocation. The number of allocations |
| // this can create is bounded by a small constant. |
| m := &uniqueMap[T]{ |
| HashTrieMap: concurrent.NewHashTrieMap[T, weak.Pointer[T]](), |
| cloneSeq: makeCloneSeq(typ), |
| } |
| a, loaded := uniqueMaps.LoadOrStore(typ, m) |
| if !loaded { |
| // Add a cleanup function for the new map. |
| cleanupFuncsMu.Lock() |
| cleanupFuncs = append(cleanupFuncs, func() { |
| // Delete all the entries whose weak references are nil and clean up |
| // deleted entries. |
| m.All()(func(key T, wp weak.Pointer[T]) bool { |
| if wp.Strong() == nil { |
| m.CompareAndDelete(key, wp) |
| } |
| return true |
| }) |
| }) |
| cleanupFuncsMu.Unlock() |
| } |
| return a.(*uniqueMap[T]) |
| } |
| |
| // setupMake is used to perform initial setup for unique.Make. |
| var setupMake sync.Once |
| |
| // startBackgroundCleanup sets up a background goroutine to occasionally call cleanupFuncs. |
| func registerCleanup() { |
| runtime_registerUniqueMapCleanup(func() { |
| // Lock for cleanup. |
| cleanupMu.Lock() |
| |
| // Grab funcs to run. |
| cleanupFuncsMu.Lock() |
| cf := cleanupFuncs |
| cleanupFuncsMu.Unlock() |
| |
| // Run cleanup. |
| for _, f := range cf { |
| f() |
| } |
| |
| // Run cleanup notifications. |
| for _, f := range cleanupNotify { |
| f() |
| } |
| cleanupNotify = nil |
| |
| // Finished. |
| cleanupMu.Unlock() |
| }) |
| } |
| |
| // Implemented in runtime. |
| |
| //go:linkname runtime_registerUniqueMapCleanup |
| func runtime_registerUniqueMapCleanup(cleanup func()) |