]> Cypherpunks repositories - gostls13.git/commitdiff
weak: move internal/weak to weak, and update according to proposal
authorMichael Anthony Knyszek <mknyszek@google.com>
Fri, 15 Nov 2024 20:42:32 +0000 (20:42 +0000)
committerGopher Robot <gobot@golang.org>
Mon, 18 Nov 2024 22:29:23 +0000 (22:29 +0000)
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>

15 files changed:
api/next/67552.txt [new file with mode: 0644]
doc/next/6-stdlib/1-weak.md [new file with mode: 0644]
doc/next/6-stdlib/99-minor/weak/67552.md [new file with mode: 0644]
src/cmd/link/internal/loader/loader.go
src/go/build/deps_test.go
src/go/doc/comment/std.go
src/internal/weak/pointer.go [deleted file]
src/runtime/gc_test.go
src/runtime/mgcsweep.go
src/runtime/mheap.go
src/unique/handle.go
src/unique/handle_test.go
src/weak/doc.go [new file with mode: 0644]
src/weak/pointer.go [new file with mode: 0644]
src/weak/pointer_test.go [moved from src/internal/weak/pointer_test.go with 93% similarity]

diff --git a/api/next/67552.txt b/api/next/67552.txt
new file mode 100644 (file)
index 0000000..5e2c21f
--- /dev/null
@@ -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
diff --git a/doc/next/6-stdlib/1-weak.md b/doc/next/6-stdlib/1-weak.md
new file mode 100644 (file)
index 0000000..1965c1f
--- /dev/null
@@ -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).
diff --git a/doc/next/6-stdlib/99-minor/weak/67552.md b/doc/next/6-stdlib/99-minor/weak/67552.md
new file mode 100644 (file)
index 0000000..e721038
--- /dev/null
@@ -0,0 +1 @@
+<!-- This is a new package; covered in 6-stdlib/1-weak.md. -->
index fe11f91526e7b7de066775a336bf00df9de52ce3..688971146d158769ec1f59a1b7b0e6942e91c7c9 100644 (file)
@@ -2336,9 +2336,6 @@ var blockedLinknames = map[string][]string{
        // coroutines
        "runtime.coroswitch": {"iter"},
        "runtime.newcoro":    {"iter"},
-       // weak references
-       "internal/weak.runtime_registerWeakPointer": {"internal/weak"},
-       "internal/weak.runtime_makeStrongFromWeak":  {"internal/weak"},
        // fips info
        "go:fipsinfo": {"crypto/internal/fips/check"},
 }
index 4d80aa7356f68540f271753d2e0646faeb7481bc..8d721a02b8b1e9315e3478bd6ee952faada1b496 100644 (file)
@@ -96,8 +96,8 @@ var depsRules = `
        < internal/runtime/maps
        < runtime
        < sync/atomic
-       < internal/weak
        < internal/sync
+       < weak
        < sync
        < internal/bisect
        < internal/godebug
index f6958512c22f3784d7dba4894fed4811ff9ff7f2..191e1f129107decfc9824d33fae17b275d9aa17a 100644 (file)
@@ -47,4 +47,5 @@ var stdPkgs = []string{
        "unicode",
        "unique",
        "unsafe",
+       "weak",
 }
diff --git a/src/internal/weak/pointer.go b/src/internal/weak/pointer.go
deleted file mode 100644 (file)
index 8e05af2..0000000
+++ /dev/null
@@ -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
index 35ddfeadc102e332b36967fa99afb7e9abe42503..35cb634936a91e1fa8bc304e3fb5e73c24a8bc63 100644 (file)
@@ -8,7 +8,6 @@ import (
        "fmt"
        "internal/asan"
        "internal/testenv"
-       "internal/weak"
        "math/bits"
        "math/rand"
        "os"
@@ -22,6 +21,7 @@ import (
        "testing"
        "time"
        "unsafe"
+       "weak"
 )
 
 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.
        // 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.
        done := make(chan struct{})
        go func() {
@@ -847,7 +847,7 @@ func TestWeakToStrongMarkTermination(t *testing.T) {
                wg.Add(1)
                go func() {
                        defer wg.Done()
-                       wp.Strong()
+                       wp.Value()
                }()
        }
 
index eb6d985ce0f6da810d88988177c944ed35eb0365..b6890bac47ec715a88957c9fc0c4c98488961232 100644 (file)
@@ -564,7 +564,7 @@ func (sl *sweepLocked) sweep(preserve bool) bool {
                        }
                        if hasFinAndRevived {
                                // 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 siter.valid() && uintptr(siter.s.offset) < endOffset {
                                        // Find the exact byte for which the special was setup
index 031c7ee9c316d9aa90cae4ff6972ded5e29d1af6..47b2d6f40a462242bb420c6bab6702e57d3a1ba9 100644 (file)
@@ -2092,12 +2092,12 @@ type specialWeakHandle struct {
        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 {
        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 {
        handle := (*atomic.Uintptr)(u)
 
index ba4b3d1687a68a373aa1cd7e06803fd27d91380a..46f2da3ddcbd95b0c2447e7a8575addbb291014d 100644 (file)
@@ -7,10 +7,10 @@ package unique
 import (
        "internal/abi"
        isync "internal/sync"
-       "internal/weak"
        "runtime"
        "sync"
        "unsafe"
+       "weak"
 )
 
 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
                // try to get the pointer we need out of it.
-               ptr = wp.Strong()
+               ptr = wp.Value()
                if ptr != nil {
                        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
                        // deleted entries.
                        m.All()(func(key T, wp weak.Pointer[T]) bool {
-                               if wp.Strong() == nil {
+                               if wp.Value() == nil {
                                        m.CompareAndDelete(key, wp)
                                }
                                return true
index e271770651b9a1e4a34cafc6e282f3ece825f876..4b70876029c81cafbc66c78e5904807b640d777c 100644 (file)
@@ -114,7 +114,7 @@ func checkMapsFor[T comparable](t *testing.T, value T) {
        if !ok {
                return
        }
-       if wp.Strong() != nil {
+       if wp.Value() != nil {
                t.Errorf("value %v still referenced a handle (or tiny block?) ", value)
                return
        }
diff --git a/src/weak/doc.go b/src/weak/doc.go
new file mode 100644 (file)
index 0000000..e6fc9b6
--- /dev/null
@@ -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
diff --git a/src/weak/pointer.go b/src/weak/pointer.go
new file mode 100644 (file)
index 0000000..f6d2053
--- /dev/null
@@ -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
similarity index 93%
rename from src/internal/weak/pointer_test.go
rename to src/weak/pointer_test.go
index 5a861bb9ca39d7a17087ad6ca9bd1d7918c37b20..213dde8c4059f84f5a1c20eaae9bac0236fe599b 100644 (file)
@@ -6,11 +6,11 @@ package weak_test
 
 import (
        "context"
-       "internal/weak"
        "runtime"
        "sync"
        "testing"
        "time"
+       "weak"
 )
 
 type T struct {
@@ -23,19 +23,19 @@ type T struct {
 func TestPointer(t *testing.T) {
        bt := new(T)
        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)
        }
        // bt is still referenced.
        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)
        }
        // bt is no longer referenced.
        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)
        }
 }
@@ -48,7 +48,7 @@ func TestPointerEquality(t *testing.T) {
                wt[i] = weak.Make(bt[i])
        }
        for i := range bt {
-               st := wt[i].Strong()
+               st := wt[i].Value()
                if 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.
        runtime.GC()
        for i := range bt {
-               st := wt[i].Strong()
+               st := wt[i].Value()
                if 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.
        runtime.GC()
        for i := range bt {
-               st := wt[i].Strong()
+               st := wt[i].Value()
                if st != nil {
                        t.Fatalf("expected weak pointer to be nil, got %p", st)
                }
@@ -101,7 +101,7 @@ func TestPointerFinalizer(t *testing.T) {
        wt := weak.Make(bt)
        done := make(chan struct{}, 1)
        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")
                }
                done <- struct{}{}
@@ -109,7 +109,7 @@ func TestPointerFinalizer(t *testing.T) {
 
        // Make sure the weak pointer stays around while bt is live.
        runtime.GC()
-       if wt.Strong() == nil {
+       if wt.Value() == nil {
                t.Errorf("weak pointer went nil too soon")
        }
        runtime.KeepAlive(bt)
@@ -118,7 +118,7 @@ func TestPointerFinalizer(t *testing.T) {
        //
        // Run one cycle to queue the finalizer.
        runtime.GC()
-       if wt.Strong() != nil {
+       if wt.Value() != nil {
                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.
        runtime.GC()
-       if wt.Strong() != nil {
+       if wt.Value() != nil {
                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:
        //
        // 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,
        // 4. The following GC cycle to mark a free object.
        //
@@ -192,7 +192,7 @@ func TestIssue69210(t *testing.T) {
                                        wt := weak.Make(bt)
                                        bt = nil
                                        time.Sleep(1 * time.Millisecond)
-                                       bt = wt.Strong()
+                                       bt = wt.Value()
                                        if bt != nil {
                                                time.Sleep(4 * time.Millisecond)
                                                bt.t = bt