diff options
| author | Michael Anthony Knyszek <mknyszek@google.com> | 2024-11-15 20:42:32 +0000 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2024-11-18 22:29:23 +0000 |
| commit | a65f1a467ff6a10dc6688f292dfa8a2e08cc1eb4 (patch) | |
| tree | 4ca29ec30c468aa3a7c2fe3ffd63a9e8d18f4389 /src/internal | |
| parent | 5e82cba9bdf45d81da549477d172f6b5e23106d4 (diff) | |
| download | go-a65f1a467ff6a10dc6688f292dfa8a2e08cc1eb4.tar.xz | |
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>
Diffstat (limited to 'src/internal')
| -rw-r--r-- | src/internal/weak/pointer.go | 83 | ||||
| -rw-r--r-- | src/internal/weak/pointer_test.go | 212 |
2 files changed, 0 insertions, 295 deletions
diff --git a/src/internal/weak/pointer.go b/src/internal/weak/pointer.go deleted file mode 100644 index 8e05af2d23..0000000000 --- a/src/internal/weak/pointer.go +++ /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 diff --git a/src/internal/weak/pointer_test.go b/src/internal/weak/pointer_test.go deleted file mode 100644 index 5a861bb9ca..0000000000 --- a/src/internal/weak/pointer_test.go +++ /dev/null @@ -1,212 +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. - -package weak_test - -import ( - "context" - "internal/weak" - "runtime" - "sync" - "testing" - "time" -) - -type T struct { - // N.B. This must contain a pointer, otherwise the weak handle might get placed - // in a tiny block making the tests in this package flaky. - t *T - a int -} - -func TestPointer(t *testing.T) { - bt := new(T) - wt := weak.Make(bt) - if st := wt.Strong(); 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 { - 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 { - t.Fatalf("expected weak pointer to be nil, got %p", st) - } -} - -func TestPointerEquality(t *testing.T) { - bt := make([]*T, 10) - wt := make([]weak.Pointer[T], 10) - for i := range bt { - bt[i] = new(T) - wt[i] = weak.Make(bt[i]) - } - for i := range bt { - st := wt[i].Strong() - if st != bt[i] { - t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i]) - } - if wp := weak.Make(st); wp != wt[i] { - t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i]) - } - if i == 0 { - continue - } - if wt[i] == wt[i-1] { - t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i]) - } - } - // bt is still referenced. - runtime.GC() - for i := range bt { - st := wt[i].Strong() - if st != bt[i] { - t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i]) - } - if wp := weak.Make(st); wp != wt[i] { - t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i]) - } - if i == 0 { - continue - } - if wt[i] == wt[i-1] { - t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i]) - } - } - bt = nil - // bt is no longer referenced. - runtime.GC() - for i := range bt { - st := wt[i].Strong() - if st != nil { - t.Fatalf("expected weak pointer to be nil, got %p", st) - } - if i == 0 { - continue - } - if wt[i] == wt[i-1] { - t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i]) - } - } -} - -func TestPointerFinalizer(t *testing.T) { - bt := new(T) - wt := weak.Make(bt) - done := make(chan struct{}, 1) - runtime.SetFinalizer(bt, func(bt *T) { - if wt.Strong() != nil { - t.Errorf("weak pointer did not go nil before finalizer ran") - } - done <- struct{}{} - }) - - // Make sure the weak pointer stays around while bt is live. - runtime.GC() - if wt.Strong() == nil { - t.Errorf("weak pointer went nil too soon") - } - runtime.KeepAlive(bt) - - // bt is no longer referenced. - // - // Run one cycle to queue the finalizer. - runtime.GC() - if wt.Strong() != nil { - t.Errorf("weak pointer did not go nil when finalizer was enqueued") - } - - // Wait for the finalizer to run. - <-done - - // The weak pointer should still be nil after the finalizer runs. - runtime.GC() - if wt.Strong() != nil { - t.Errorf("weak pointer is non-nil even after finalization: %v", wt) - } -} - -// Regression test for issue 69210. -// -// Weak-to-strong conversions must shade the new strong pointer, otherwise -// that might be creating the only strong pointer to a white object which -// is hidden in a blackened stack. -// -// Never fails if correct, fails with some high probability if incorrect. -func TestIssue69210(t *testing.T) { - if testing.Short() { - t.Skip("this is a stress test that takes seconds to run on its own") - } - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - // What we're trying to do is manufacture the conditions under which this - // 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, - // 3. The new strong pointer to be missed by the GC, - // 4. The following GC cycle to mark a free object. - // - // Unfortunately, (2) and (3) are hard to control, but we can increase - // the likelihood by having several goroutines do (1) at once while - // another goroutine constantly keeps us in the GC with runtime.GC. - // Like throwing darts at a dart board until they land just right. - // We can increase the likelihood of (4) by adding some delay after - // creating the strong pointer, but only if it's non-nil. If it's nil, - // that means it was already collected in which case there's no chance - // of triggering the bug, so we want to retry as fast as possible. - // Our heap here is tiny, so the GCs will go by fast. - // - // As of 2024-09-03, removing the line that shades pointers during - // the weak-to-strong conversion causes this test to fail about 50% - // of the time. - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - for { - runtime.GC() - - select { - case <-ctx.Done(): - return - default: - } - } - }() - for range max(runtime.GOMAXPROCS(-1)-1, 1) { - wg.Add(1) - go func() { - defer wg.Done() - for { - for range 5 { - bt := new(T) - wt := weak.Make(bt) - bt = nil - time.Sleep(1 * time.Millisecond) - bt = wt.Strong() - if bt != nil { - time.Sleep(4 * time.Millisecond) - bt.t = bt - bt.a = 12 - } - runtime.KeepAlive(bt) - } - select { - case <-ctx.Done(): - return - default: - } - } - }() - } - wg.Wait() -} |
