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 unique
import (
_
)
// 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.TypeOf()
, := 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.Mutex
cleanupFuncsMu sync.Mutex
cleanupFuncs []func()
cleanupNotify []func() // One-time notifications when cleanups finish.
)
type uniqueMap[ comparable] struct {
*concurrent.HashTrieMap[, weak.Pointer[]]
cloneSeq
}
func addUniqueMap[ 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 registerCleanup() {
runtime_registerUniqueMapCleanup(func() {
// Lock for cleanup.
cleanupMu.Lock()
// Grab funcs to run.
cleanupFuncsMu.Lock()
:= cleanupFuncs
cleanupFuncsMu.Unlock()
// Run cleanup.
for , := range {
()
}
// Run cleanup notifications.
for , := range cleanupNotify {
()
}
cleanupNotify = nil
// Finished.
cleanupMu.Unlock()
})
}
// Implemented in runtime.
//go:linkname runtime_registerUniqueMapCleanup
func runtime_registerUniqueMapCleanup( func())
The pages are generated with Golds v0.7.0-preview. (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. |