1
0
mirror of https://github.com/golang/go synced 2024-11-25 19:07:57 -07:00

weak: move internal/weak to weak, and update according to proposal

The updates are:
- API documentation changes.
- Removal of the old package documentation discouraging linkname.
- Addition of new package documentation with some advice.
- Renaming of weak.Pointer.Strong -> weak.Pointer.Value.

Fixes #67552.

Change-Id: Ifad7e629b6d339dacaf2ca37b459d7f903e31bf8
Reviewed-on: https://go-review.googlesource.com/c/go/+/628455
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
Michael Anthony Knyszek 2024-11-15 20:42:32 +00:00 committed by Gopher Robot
parent 5e82cba9bd
commit a65f1a467f
15 changed files with 129 additions and 110 deletions

3
api/next/67552.txt Normal file
View File

@ -0,0 +1,3 @@
pkg weak, func Make[$0 interface{}](*$0) Pointer[$0] #67552
pkg weak, method (Pointer[$0]) Value() *$0 #67552
pkg weak, type Pointer[$0 interface{}] struct #67552

View File

@ -0,0 +1,12 @@
### New weak package
The new [weak](/pkg/weak) package provides weak pointers.
Weak pointers are a low-level primitive provided to enable the
creation of memory-efficient structures, such as weak maps for
associating values, canonicalization maps for anything not
covered by package [unique](/pkg/unique), and various kinds
of caches.
For supporting these use-cases, this release also provides
[runtime.AddCleanup](/pkg/runtime#AddCleanup) and
[maphash.Comparable](/pkg/maphash#Comparable).

View File

@ -0,0 +1 @@
<!-- This is a new package; covered in 6-stdlib/1-weak.md. -->

View File

@ -2336,9 +2336,6 @@ var blockedLinknames = map[string][]string{
// coroutines // coroutines
"runtime.coroswitch": {"iter"}, "runtime.coroswitch": {"iter"},
"runtime.newcoro": {"iter"}, "runtime.newcoro": {"iter"},
// weak references
"internal/weak.runtime_registerWeakPointer": {"internal/weak"},
"internal/weak.runtime_makeStrongFromWeak": {"internal/weak"},
// fips info // fips info
"go:fipsinfo": {"crypto/internal/fips/check"}, "go:fipsinfo": {"crypto/internal/fips/check"},
} }

View File

@ -96,8 +96,8 @@ var depsRules = `
< internal/runtime/maps < internal/runtime/maps
< runtime < runtime
< sync/atomic < sync/atomic
< internal/weak
< internal/sync < internal/sync
< weak
< sync < sync
< internal/bisect < internal/bisect
< internal/godebug < internal/godebug

View File

@ -47,4 +47,5 @@ var stdPkgs = []string{
"unicode", "unicode",
"unique", "unique",
"unsafe", "unsafe",
"weak",
} }

View File

@ -1,83 +0,0 @@
// 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.
/*
The weak package is a package for managing weak pointers.
Weak pointers are pointers that explicitly do not keep a value live and
must be queried for a regular Go pointer.
The result of such a query may be observed as nil at any point after a
weakly-pointed-to object becomes eligible for reclamation by the garbage
collector.
More specifically, weak pointers become nil as soon as the garbage collector
identifies that the object is unreachable, before it is made reachable
again by a finalizer.
In terms of the C# language, these semantics are roughly equivalent to the
the semantics of "short" weak references.
In terms of the Java language, these semantics are roughly equivalent to the
semantics of the WeakReference type.
Using go:linkname to access this package and the functions it references
is explicitly forbidden by the toolchain because the semantics of this
package have not gone through the proposal process. By exposing this
functionality, we risk locking in the existing semantics due to Hyrum's Law.
If you believe you have a good use-case for weak references not already
covered by the standard library, file a proposal issue at
https://github.com/golang/go/issues instead of relying on this package.
*/
package weak
import (
"internal/abi"
"runtime"
"unsafe"
)
// Pointer is a weak pointer to a value of type T.
//
// This value is comparable is guaranteed to compare equal if the pointers
// that they were created from compare equal. This property is retained even
// after the object referenced by the pointer used to create a weak reference
// is reclaimed.
//
// If multiple weak pointers are made to different offsets within same object
// (for example, pointers to different fields of the same struct), those pointers
// will not compare equal.
// If a weak pointer is created from an object that becomes reachable again due
// to a finalizer, that weak pointer will not compare equal with weak pointers
// created before it became unreachable.
type Pointer[T any] struct {
u unsafe.Pointer
}
// Make creates a weak pointer from a strong pointer to some value of type T.
func Make[T any](ptr *T) Pointer[T] {
// Explicitly force ptr to escape to the heap.
ptr = abi.Escape(ptr)
var u unsafe.Pointer
if ptr != nil {
u = runtime_registerWeakPointer(unsafe.Pointer(ptr))
}
runtime.KeepAlive(ptr)
return Pointer[T]{u}
}
// Strong creates a strong pointer from the weak pointer.
// Returns nil if the original value for the weak pointer was reclaimed by
// the garbage collector.
// If a weak pointer points to an object with a finalizer, then Strong will
// return nil as soon as the object's finalizer is queued for execution.
func (p Pointer[T]) Strong() *T {
return (*T)(runtime_makeStrongFromWeak(p.u))
}
// Implemented in runtime.
//go:linkname runtime_registerWeakPointer
func runtime_registerWeakPointer(unsafe.Pointer) unsafe.Pointer
//go:linkname runtime_makeStrongFromWeak
func runtime_makeStrongFromWeak(unsafe.Pointer) unsafe.Pointer

View File

@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"internal/asan" "internal/asan"
"internal/testenv" "internal/testenv"
"internal/weak"
"math/bits" "math/bits"
"math/rand" "math/rand"
"os" "os"
@ -22,6 +21,7 @@ import (
"testing" "testing"
"time" "time"
"unsafe" "unsafe"
"weak"
) )
func TestGcSys(t *testing.T) { func TestGcSys(t *testing.T) {
@ -826,7 +826,7 @@ func TestWeakToStrongMarkTermination(t *testing.T) {
// Start a GC, and wait a little bit to get something spinning in mark termination. // Start a GC, and wait a little bit to get something spinning in mark termination.
// Simultaneously, fire off another goroutine to disable spinning. If everything's // Simultaneously, fire off another goroutine to disable spinning. If everything's
// working correctly, then weak.Strong will block, so we need to make sure something // working correctly, then weak.Value will block, so we need to make sure something
// prevents the GC from continuing to spin. // prevents the GC from continuing to spin.
done := make(chan struct{}) done := make(chan struct{})
go func() { go func() {
@ -847,7 +847,7 @@ func TestWeakToStrongMarkTermination(t *testing.T) {
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
wp.Strong() wp.Value()
}() }()
} }

View File

@ -564,7 +564,7 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
} }
if hasFinAndRevived { if hasFinAndRevived {
// Pass 2: queue all finalizers and clear any weak handles. Weak handles are cleared // Pass 2: queue all finalizers and clear any weak handles. Weak handles are cleared
// before finalization as specified by the internal/weak package. See the documentation // before finalization as specified by the weak package. See the documentation
// for that package for more details. // for that package for more details.
for siter.valid() && uintptr(siter.s.offset) < endOffset { for siter.valid() && uintptr(siter.s.offset) < endOffset {
// Find the exact byte for which the special was setup // Find the exact byte for which the special was setup

View File

@ -2092,12 +2092,12 @@ type specialWeakHandle struct {
handle *atomic.Uintptr handle *atomic.Uintptr
} }
//go:linkname internal_weak_runtime_registerWeakPointer internal/weak.runtime_registerWeakPointer //go:linkname internal_weak_runtime_registerWeakPointer weak.runtime_registerWeakPointer
func internal_weak_runtime_registerWeakPointer(p unsafe.Pointer) unsafe.Pointer { func internal_weak_runtime_registerWeakPointer(p unsafe.Pointer) unsafe.Pointer {
return unsafe.Pointer(getOrAddWeakHandle(unsafe.Pointer(p))) return unsafe.Pointer(getOrAddWeakHandle(unsafe.Pointer(p)))
} }
//go:linkname internal_weak_runtime_makeStrongFromWeak internal/weak.runtime_makeStrongFromWeak //go:linkname internal_weak_runtime_makeStrongFromWeak weak.runtime_makeStrongFromWeak
func internal_weak_runtime_makeStrongFromWeak(u unsafe.Pointer) unsafe.Pointer { func internal_weak_runtime_makeStrongFromWeak(u unsafe.Pointer) unsafe.Pointer {
handle := (*atomic.Uintptr)(u) handle := (*atomic.Uintptr)(u)

View File

@ -7,10 +7,10 @@ package unique
import ( import (
"internal/abi" "internal/abi"
isync "internal/sync" isync "internal/sync"
"internal/weak"
"runtime" "runtime"
"sync" "sync"
"unsafe" "unsafe"
"weak"
) )
var zero uintptr var zero uintptr
@ -76,7 +76,7 @@ func Make[T comparable](value T) Handle[T] {
} }
// Now that we're sure there's a value in the map, let's // Now that we're sure there's a value in the map, let's
// try to get the pointer we need out of it. // try to get the pointer we need out of it.
ptr = wp.Strong() ptr = wp.Value()
if ptr != nil { if ptr != nil {
break break
} }
@ -132,7 +132,7 @@ func addUniqueMap[T comparable](typ *abi.Type) *uniqueMap[T] {
// Delete all the entries whose weak references are nil and clean up // Delete all the entries whose weak references are nil and clean up
// deleted entries. // deleted entries.
m.All()(func(key T, wp weak.Pointer[T]) bool { m.All()(func(key T, wp weak.Pointer[T]) bool {
if wp.Strong() == nil { if wp.Value() == nil {
m.CompareAndDelete(key, wp) m.CompareAndDelete(key, wp)
} }
return true return true

View File

@ -114,7 +114,7 @@ func checkMapsFor[T comparable](t *testing.T, value T) {
if !ok { if !ok {
return return
} }
if wp.Strong() != nil { if wp.Value() != nil {
t.Errorf("value %v still referenced a handle (or tiny block?) ", value) t.Errorf("value %v still referenced a handle (or tiny block?) ", value)
return return
} }

26
src/weak/doc.go Normal file
View File

@ -0,0 +1,26 @@
// 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 weak provides weak pointers with the goal of memory efficiency.
The primary use-cases for weak pointers are for implementing caches,
canonicalization maps (like the unique package), and for tying together
the lifetimes of separate values.
## Advice
This package is intended to target niche use-cases like the unique
package, not as a general replacement for regular Go pointers, maps,
etc.
Misuse of the structures in this package will generate unexpected and
hard-to-reproduce bugs.
Using the facilities in this package to try and resolve out-of-memory
issues and/or memory leaks is very likely the wrong answer.
The structures in this package are intended to be an implementation
detail of the package they are used by (again, see the unique package).
Avoid exposing weak structures across API boundaries, since that exposes
users of your package to the subtleties of this package.
*/
package weak

62
src/weak/pointer.go Normal file
View File

@ -0,0 +1,62 @@
// 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 weak
import (
"internal/abi"
"runtime"
"unsafe"
)
// Pointer is a weak pointer to a value of type T.
//
// Two Pointer values compare equal if the pointers
// that they were created from compare equal. This property is retained even
// after the object referenced by the pointer used to create a weak reference
// is reclaimed.
//
// If multiple weak pointers are made to different offsets within same object
// (for example, pointers to different fields of the same struct), those pointers
// will not compare equal.
// If a weak pointer is created from an object that becomes unreachable, but is
// then resurrected due to a finalizer, that weak pointer will not compare equal
// with weak pointers created after resurrection.
//
// Calling Make with a nil pointer returns a weak pointer whose Value method
// always returns nil. The zero value of a Pointer behaves as if it was created
// by passing nil to Make and compares equal with such pointers.
type Pointer[T any] struct {
u unsafe.Pointer
}
// Make creates a weak pointer from a strong pointer to some value of type T.
func Make[T any](ptr *T) Pointer[T] {
// Explicitly force ptr to escape to the heap.
ptr = abi.Escape(ptr)
var u unsafe.Pointer
if ptr != nil {
u = runtime_registerWeakPointer(unsafe.Pointer(ptr))
}
runtime.KeepAlive(ptr)
return Pointer[T]{u}
}
// Value returns the original pointer used to create the weak pointer.
// It returns nil if the value pointed to by the original pointer was reclaimed by
// the garbage collector.
// If a weak pointer points to an object with a finalizer, then Value will
// return nil as soon as the object's finalizer is queued for execution.
func (p Pointer[T]) Value() *T {
return (*T)(runtime_makeStrongFromWeak(p.u))
}
// Implemented in runtime.
//go:linkname runtime_registerWeakPointer
func runtime_registerWeakPointer(unsafe.Pointer) unsafe.Pointer
//go:linkname runtime_makeStrongFromWeak
func runtime_makeStrongFromWeak(unsafe.Pointer) unsafe.Pointer

View File

@ -6,11 +6,11 @@ package weak_test
import ( import (
"context" "context"
"internal/weak"
"runtime" "runtime"
"sync" "sync"
"testing" "testing"
"time" "time"
"weak"
) )
type T struct { type T struct {
@ -23,19 +23,19 @@ type T struct {
func TestPointer(t *testing.T) { func TestPointer(t *testing.T) {
bt := new(T) bt := new(T)
wt := weak.Make(bt) wt := weak.Make(bt)
if st := wt.Strong(); st != bt { if st := wt.Value(); st != bt {
t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt) t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt)
} }
// bt is still referenced. // bt is still referenced.
runtime.GC() runtime.GC()
if st := wt.Strong(); st != bt { if st := wt.Value(); st != bt {
t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt) t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt)
} }
// bt is no longer referenced. // bt is no longer referenced.
runtime.GC() runtime.GC()
if st := wt.Strong(); st != nil { if st := wt.Value(); st != nil {
t.Fatalf("expected weak pointer to be nil, got %p", st) t.Fatalf("expected weak pointer to be nil, got %p", st)
} }
} }
@ -48,7 +48,7 @@ func TestPointerEquality(t *testing.T) {
wt[i] = weak.Make(bt[i]) wt[i] = weak.Make(bt[i])
} }
for i := range bt { for i := range bt {
st := wt[i].Strong() st := wt[i].Value()
if st != bt[i] { if st != bt[i] {
t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i]) t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
} }
@ -65,7 +65,7 @@ func TestPointerEquality(t *testing.T) {
// bt is still referenced. // bt is still referenced.
runtime.GC() runtime.GC()
for i := range bt { for i := range bt {
st := wt[i].Strong() st := wt[i].Value()
if st != bt[i] { if st != bt[i] {
t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i]) t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
} }
@ -83,7 +83,7 @@ func TestPointerEquality(t *testing.T) {
// bt is no longer referenced. // bt is no longer referenced.
runtime.GC() runtime.GC()
for i := range bt { for i := range bt {
st := wt[i].Strong() st := wt[i].Value()
if st != nil { if st != nil {
t.Fatalf("expected weak pointer to be nil, got %p", st) t.Fatalf("expected weak pointer to be nil, got %p", st)
} }
@ -101,7 +101,7 @@ func TestPointerFinalizer(t *testing.T) {
wt := weak.Make(bt) wt := weak.Make(bt)
done := make(chan struct{}, 1) done := make(chan struct{}, 1)
runtime.SetFinalizer(bt, func(bt *T) { runtime.SetFinalizer(bt, func(bt *T) {
if wt.Strong() != nil { if wt.Value() != nil {
t.Errorf("weak pointer did not go nil before finalizer ran") t.Errorf("weak pointer did not go nil before finalizer ran")
} }
done <- struct{}{} done <- struct{}{}
@ -109,7 +109,7 @@ func TestPointerFinalizer(t *testing.T) {
// Make sure the weak pointer stays around while bt is live. // Make sure the weak pointer stays around while bt is live.
runtime.GC() runtime.GC()
if wt.Strong() == nil { if wt.Value() == nil {
t.Errorf("weak pointer went nil too soon") t.Errorf("weak pointer went nil too soon")
} }
runtime.KeepAlive(bt) runtime.KeepAlive(bt)
@ -118,7 +118,7 @@ func TestPointerFinalizer(t *testing.T) {
// //
// Run one cycle to queue the finalizer. // Run one cycle to queue the finalizer.
runtime.GC() runtime.GC()
if wt.Strong() != nil { if wt.Value() != nil {
t.Errorf("weak pointer did not go nil when finalizer was enqueued") t.Errorf("weak pointer did not go nil when finalizer was enqueued")
} }
@ -127,7 +127,7 @@ func TestPointerFinalizer(t *testing.T) {
// The weak pointer should still be nil after the finalizer runs. // The weak pointer should still be nil after the finalizer runs.
runtime.GC() runtime.GC()
if wt.Strong() != nil { if wt.Value() != nil {
t.Errorf("weak pointer is non-nil even after finalization: %v", wt) t.Errorf("weak pointer is non-nil even after finalization: %v", wt)
} }
} }
@ -150,7 +150,7 @@ func TestIssue69210(t *testing.T) {
// bug happens. Specifically, we want: // bug happens. Specifically, we want:
// //
// 1. To create a whole bunch of objects that are only weakly-pointed-to, // 1. To create a whole bunch of objects that are only weakly-pointed-to,
// 2. To call Strong while the GC is in the mark phase, // 2. To call Value while the GC is in the mark phase,
// 3. The new strong pointer to be missed by the GC, // 3. The new strong pointer to be missed by the GC,
// 4. The following GC cycle to mark a free object. // 4. The following GC cycle to mark a free object.
// //
@ -192,7 +192,7 @@ func TestIssue69210(t *testing.T) {
wt := weak.Make(bt) wt := weak.Make(bt)
bt = nil bt = nil
time.Sleep(1 * time.Millisecond) time.Sleep(1 * time.Millisecond)
bt = wt.Strong() bt = wt.Value()
if bt != nil { if bt != nil {
time.Sleep(4 * time.Millisecond) time.Sleep(4 * time.Millisecond)
bt.t = bt bt.t = bt