Source File
handle.go
Belonging Package
unique
// 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 uniqueimport (_)// 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[ comparable] struct {value *}// Value returns a shallow copy of the T value that produced the Handle.func ( Handle[]) () {return *.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.func [ comparable]( ) Handle[] {// Find the map for type T.:= abi.TypeFor[](), := uniqueMaps.Load()if ! {// 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)= addUniqueMap[]()}:= .(*uniqueMap[])// 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 (* // Keep this around to keep it alive.weak.Pointer[]):= func() (, weak.Pointer[]) {if == nil {= new()* = clone(, &.cloneSeq)= weak.Make()}return *,}var *for {// Check the map., := .Load()if ! {// Try to insert a new value into the map., := (), _ = .LoadOrStore(, )}// Now that we're sure there's a value in the map, let's// try to get the pointer we need out of it.= .Strong()if != nil {break}// The weak pointer is nil, so the old value is truly dead.// Try to remove it and start over..CompareAndDelete(, )}runtime.KeepAlive()return Handle[]{}}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.MutexcleanupFuncsMu sync.MutexcleanupFuncs []func()cleanupNotify []func() // One-time notifications when cleanups finish.)type uniqueMap[ comparable] struct {*concurrent.HashTrieMap[, weak.Pointer[]]cloneSeq}func [ comparable]( *abi.Type) *uniqueMap[] {// 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.:= &uniqueMap[]{HashTrieMap: concurrent.NewHashTrieMap[, weak.Pointer[]](),cloneSeq: makeCloneSeq(),}, := uniqueMaps.LoadOrStore(, )if ! {// 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..All()(func( , weak.Pointer[]) bool {if .Strong() == nil {.CompareAndDelete(, )}return true})})cleanupFuncsMu.Unlock()}return .(*uniqueMap[])}// 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 () {runtime_registerUniqueMapCleanup(func() {// Lock for cleanup.cleanupMu.Lock()// Grab funcs to run.cleanupFuncsMu.Lock():= cleanupFuncscleanupFuncsMu.Unlock()// Run cleanup.for , := range {()}// Run cleanup notifications.for , := range cleanupNotify {()}cleanupNotify = nil// Finished.cleanupMu.Unlock()})}// Implemented in runtime.//go:linkname runtime_registerUniqueMapCleanupfunc ( func())
![]() |
The pages are generated with Golds v0.7.6. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |