summaryrefslogtreecommitdiffstats
path: root/src/runtime/pinner_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/runtime/pinner_test.go')
-rw-r--r--src/runtime/pinner_test.go540
1 files changed, 540 insertions, 0 deletions
diff --git a/src/runtime/pinner_test.go b/src/runtime/pinner_test.go
new file mode 100644
index 0000000..ef8500c
--- /dev/null
+++ b/src/runtime/pinner_test.go
@@ -0,0 +1,540 @@
+// Copyright 2023 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 runtime_test
+
+import (
+ "runtime"
+ "testing"
+ "time"
+ "unsafe"
+)
+
+type obj struct {
+ x int64
+ y int64
+ z int64
+}
+
+type objWith[T any] struct {
+ x int64
+ y int64
+ z int64
+ o T
+}
+
+var (
+ globalUintptr uintptr
+ globalPtrToObj = &obj{}
+ globalPtrToObjWithPtr = &objWith[*uintptr]{}
+ globalPtrToRuntimeObj = func() *obj { return &obj{} }()
+ globalPtrToRuntimeObjWithPtr = func() *objWith[*uintptr] { return &objWith[*uintptr]{} }()
+)
+
+func assertDidPanic(t *testing.T) {
+ if recover() == nil {
+ t.Fatal("did not panic")
+ }
+}
+
+func assertCgoCheckPanics(t *testing.T, p any) {
+ defer func() {
+ if recover() == nil {
+ t.Fatal("cgoCheckPointer() did not panic, make sure the tests run with cgocheck=1")
+ }
+ }()
+ runtime.CgoCheckPointer(p, true)
+}
+
+func TestPinnerSimple(t *testing.T) {
+ var pinner runtime.Pinner
+ p := new(obj)
+ addr := unsafe.Pointer(p)
+ if runtime.IsPinned(addr) {
+ t.Fatal("already marked as pinned")
+ }
+ pinner.Pin(p)
+ if !runtime.IsPinned(addr) {
+ t.Fatal("not marked as pinned")
+ }
+ if runtime.GetPinCounter(addr) != nil {
+ t.Fatal("pin counter should not exist")
+ }
+ pinner.Unpin()
+ if runtime.IsPinned(addr) {
+ t.Fatal("still marked as pinned")
+ }
+}
+
+func TestPinnerPinKeepsAliveAndReleases(t *testing.T) {
+ var pinner runtime.Pinner
+ p := new(obj)
+ done := make(chan struct{})
+ runtime.SetFinalizer(p, func(any) {
+ done <- struct{}{}
+ })
+ pinner.Pin(p)
+ p = nil
+ runtime.GC()
+ runtime.GC()
+ select {
+ case <-done:
+ t.Fatal("Pin() didn't keep object alive")
+ case <-time.After(time.Millisecond * 10):
+ break
+ }
+ pinner.Unpin()
+ runtime.GC()
+ runtime.GC()
+ select {
+ case <-done:
+ break
+ case <-time.After(time.Second):
+ t.Fatal("Unpin() didn't release object")
+ }
+}
+
+func TestPinnerMultiplePinsSame(t *testing.T) {
+ const N = 100
+ var pinner runtime.Pinner
+ p := new(obj)
+ addr := unsafe.Pointer(p)
+ if runtime.IsPinned(addr) {
+ t.Fatal("already marked as pinned")
+ }
+ for i := 0; i < N; i++ {
+ pinner.Pin(p)
+ }
+ if !runtime.IsPinned(addr) {
+ t.Fatal("not marked as pinned")
+ }
+ if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != N-1 {
+ t.Fatalf("pin counter incorrect: %d", *cnt)
+ }
+ pinner.Unpin()
+ if runtime.IsPinned(addr) {
+ t.Fatal("still marked as pinned")
+ }
+ if runtime.GetPinCounter(addr) != nil {
+ t.Fatal("pin counter was not deleted")
+ }
+}
+
+func TestPinnerTwoPinner(t *testing.T) {
+ var pinner1, pinner2 runtime.Pinner
+ p := new(obj)
+ addr := unsafe.Pointer(p)
+ if runtime.IsPinned(addr) {
+ t.Fatal("already marked as pinned")
+ }
+ pinner1.Pin(p)
+ if !runtime.IsPinned(addr) {
+ t.Fatal("not marked as pinned")
+ }
+ if runtime.GetPinCounter(addr) != nil {
+ t.Fatal("pin counter should not exist")
+ }
+ pinner2.Pin(p)
+ if !runtime.IsPinned(addr) {
+ t.Fatal("not marked as pinned")
+ }
+ if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != 1 {
+ t.Fatalf("pin counter incorrect: %d", *cnt)
+ }
+ pinner1.Unpin()
+ if !runtime.IsPinned(addr) {
+ t.Fatal("not marked as pinned")
+ }
+ if runtime.GetPinCounter(addr) != nil {
+ t.Fatal("pin counter should not exist")
+ }
+ pinner2.Unpin()
+ if runtime.IsPinned(addr) {
+ t.Fatal("still marked as pinned")
+ }
+ if runtime.GetPinCounter(addr) != nil {
+ t.Fatal("pin counter was not deleted")
+ }
+}
+
+func TestPinnerPinZerosizeObj(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ p := new(struct{})
+ pinner.Pin(p)
+ if !runtime.IsPinned(unsafe.Pointer(p)) {
+ t.Fatal("not marked as pinned")
+ }
+}
+
+func TestPinnerPinGlobalPtr(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ pinner.Pin(globalPtrToObj)
+ pinner.Pin(globalPtrToObjWithPtr)
+ pinner.Pin(globalPtrToRuntimeObj)
+ pinner.Pin(globalPtrToRuntimeObjWithPtr)
+}
+
+func TestPinnerPinTinyObj(t *testing.T) {
+ var pinner runtime.Pinner
+ const N = 64
+ var addr [N]unsafe.Pointer
+ for i := 0; i < N; i++ {
+ p := new(bool)
+ addr[i] = unsafe.Pointer(p)
+ pinner.Pin(p)
+ pinner.Pin(p)
+ if !runtime.IsPinned(addr[i]) {
+ t.Fatalf("not marked as pinned: %d", i)
+ }
+ if cnt := runtime.GetPinCounter(addr[i]); cnt == nil || *cnt == 0 {
+ t.Fatalf("pin counter incorrect: %d, %d", *cnt, i)
+ }
+ }
+ pinner.Unpin()
+ for i := 0; i < N; i++ {
+ if runtime.IsPinned(addr[i]) {
+ t.Fatal("still marked as pinned")
+ }
+ if runtime.GetPinCounter(addr[i]) != nil {
+ t.Fatal("pin counter should not exist")
+ }
+ }
+}
+
+func TestPinnerInterface(t *testing.T) {
+ var pinner runtime.Pinner
+ o := new(obj)
+ ifc := any(o)
+ pinner.Pin(&ifc)
+ if !runtime.IsPinned(unsafe.Pointer(&ifc)) {
+ t.Fatal("not marked as pinned")
+ }
+ if runtime.IsPinned(unsafe.Pointer(o)) {
+ t.Fatal("marked as pinned")
+ }
+ pinner.Unpin()
+ pinner.Pin(ifc)
+ if !runtime.IsPinned(unsafe.Pointer(o)) {
+ t.Fatal("not marked as pinned")
+ }
+ if runtime.IsPinned(unsafe.Pointer(&ifc)) {
+ t.Fatal("marked as pinned")
+ }
+ pinner.Unpin()
+}
+
+func TestPinnerPinNonPtrPanics(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ var i int
+ defer assertDidPanic(t)
+ pinner.Pin(i)
+}
+
+func TestPinnerReuse(t *testing.T) {
+ var pinner runtime.Pinner
+ p := new(obj)
+ p2 := &p
+ assertCgoCheckPanics(t, p2)
+ pinner.Pin(p)
+ runtime.CgoCheckPointer(p2, true)
+ pinner.Unpin()
+ assertCgoCheckPanics(t, p2)
+ pinner.Pin(p)
+ runtime.CgoCheckPointer(p2, true)
+ pinner.Unpin()
+}
+
+func TestPinnerEmptyUnpin(t *testing.T) {
+ var pinner runtime.Pinner
+ pinner.Unpin()
+ pinner.Unpin()
+}
+
+func TestPinnerLeakPanics(t *testing.T) {
+ old := runtime.GetPinnerLeakPanic()
+ func() {
+ defer assertDidPanic(t)
+ old()
+ }()
+ done := make(chan struct{})
+ runtime.SetPinnerLeakPanic(func() {
+ done <- struct{}{}
+ })
+ func() {
+ var pinner runtime.Pinner
+ p := new(obj)
+ pinner.Pin(p)
+ }()
+ runtime.GC()
+ runtime.GC()
+ select {
+ case <-done:
+ break
+ case <-time.After(time.Second):
+ t.Fatal("leak didn't make GC to panic")
+ }
+ runtime.SetPinnerLeakPanic(old)
+}
+
+func TestPinnerCgoCheckPtr2Ptr(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ p := new(obj)
+ p2 := &objWith[*obj]{o: p}
+ assertCgoCheckPanics(t, p2)
+ pinner.Pin(p)
+ runtime.CgoCheckPointer(p2, true)
+}
+
+func TestPinnerCgoCheckPtr2UnsafePtr(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ p := unsafe.Pointer(new(obj))
+ p2 := &objWith[unsafe.Pointer]{o: p}
+ assertCgoCheckPanics(t, p2)
+ pinner.Pin(p)
+ runtime.CgoCheckPointer(p2, true)
+}
+
+func TestPinnerCgoCheckPtr2UnknownPtr(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ p := unsafe.Pointer(new(obj))
+ p2 := &p
+ func() {
+ defer assertDidPanic(t)
+ runtime.CgoCheckPointer(p2, nil)
+ }()
+ pinner.Pin(p)
+ runtime.CgoCheckPointer(p2, nil)
+}
+
+func TestPinnerCgoCheckInterface(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ var ifc any
+ var o obj
+ ifc = &o
+ p := &ifc
+ assertCgoCheckPanics(t, p)
+ pinner.Pin(&o)
+ runtime.CgoCheckPointer(p, true)
+}
+
+func TestPinnerCgoCheckSlice(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ sl := []int{1, 2, 3}
+ assertCgoCheckPanics(t, &sl)
+ pinner.Pin(&sl[0])
+ runtime.CgoCheckPointer(&sl, true)
+}
+
+func TestPinnerCgoCheckString(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ b := []byte("foobar")
+ str := unsafe.String(&b[0], 6)
+ assertCgoCheckPanics(t, &str)
+ pinner.Pin(&b[0])
+ runtime.CgoCheckPointer(&str, true)
+}
+
+func TestPinnerCgoCheckPinned2UnpinnedPanics(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ p := new(obj)
+ p2 := &objWith[*obj]{o: p}
+ assertCgoCheckPanics(t, p2)
+ pinner.Pin(p2)
+ assertCgoCheckPanics(t, p2)
+}
+
+func TestPinnerCgoCheckPtr2Pinned2Unpinned(t *testing.T) {
+ var pinner runtime.Pinner
+ defer pinner.Unpin()
+ p := new(obj)
+ p2 := &objWith[*obj]{o: p}
+ p3 := &objWith[*objWith[*obj]]{o: p2}
+ assertCgoCheckPanics(t, p2)
+ assertCgoCheckPanics(t, p3)
+ pinner.Pin(p2)
+ assertCgoCheckPanics(t, p2)
+ assertCgoCheckPanics(t, p3)
+ pinner.Pin(p)
+ runtime.CgoCheckPointer(p2, true)
+ runtime.CgoCheckPointer(p3, true)
+}
+
+func BenchmarkPinnerPinUnpinBatch(b *testing.B) {
+ const Batch = 1000
+ var data [Batch]*obj
+ for i := 0; i < Batch; i++ {
+ data[i] = new(obj)
+ }
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ var pinner runtime.Pinner
+ for i := 0; i < Batch; i++ {
+ pinner.Pin(data[i])
+ }
+ pinner.Unpin()
+ }
+}
+
+func BenchmarkPinnerPinUnpinBatchDouble(b *testing.B) {
+ const Batch = 1000
+ var data [Batch]*obj
+ for i := 0; i < Batch; i++ {
+ data[i] = new(obj)
+ }
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ var pinner runtime.Pinner
+ for i := 0; i < Batch; i++ {
+ pinner.Pin(data[i])
+ pinner.Pin(data[i])
+ }
+ pinner.Unpin()
+ }
+}
+
+func BenchmarkPinnerPinUnpinBatchTiny(b *testing.B) {
+ const Batch = 1000
+ var data [Batch]*bool
+ for i := 0; i < Batch; i++ {
+ data[i] = new(bool)
+ }
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ var pinner runtime.Pinner
+ for i := 0; i < Batch; i++ {
+ pinner.Pin(data[i])
+ }
+ pinner.Unpin()
+ }
+}
+
+func BenchmarkPinnerPinUnpin(b *testing.B) {
+ p := new(obj)
+ for n := 0; n < b.N; n++ {
+ var pinner runtime.Pinner
+ pinner.Pin(p)
+ pinner.Unpin()
+ }
+}
+
+func BenchmarkPinnerPinUnpinTiny(b *testing.B) {
+ p := new(bool)
+ for n := 0; n < b.N; n++ {
+ var pinner runtime.Pinner
+ pinner.Pin(p)
+ pinner.Unpin()
+ }
+}
+
+func BenchmarkPinnerPinUnpinDouble(b *testing.B) {
+ p := new(obj)
+ for n := 0; n < b.N; n++ {
+ var pinner runtime.Pinner
+ pinner.Pin(p)
+ pinner.Pin(p)
+ pinner.Unpin()
+ }
+}
+
+func BenchmarkPinnerPinUnpinParallel(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ p := new(obj)
+ for pb.Next() {
+ var pinner runtime.Pinner
+ pinner.Pin(p)
+ pinner.Unpin()
+ }
+ })
+}
+
+func BenchmarkPinnerPinUnpinParallelTiny(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ p := new(bool)
+ for pb.Next() {
+ var pinner runtime.Pinner
+ pinner.Pin(p)
+ pinner.Unpin()
+ }
+ })
+}
+
+func BenchmarkPinnerPinUnpinParallelDouble(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ p := new(obj)
+ for pb.Next() {
+ var pinner runtime.Pinner
+ pinner.Pin(p)
+ pinner.Pin(p)
+ pinner.Unpin()
+ }
+ })
+}
+
+func BenchmarkPinnerIsPinnedOnPinned(b *testing.B) {
+ var pinner runtime.Pinner
+ ptr := new(obj)
+ pinner.Pin(ptr)
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ runtime.IsPinned(unsafe.Pointer(ptr))
+ }
+ pinner.Unpin()
+}
+
+func BenchmarkPinnerIsPinnedOnUnpinned(b *testing.B) {
+ ptr := new(obj)
+ b.ResetTimer()
+ for n := 0; n < b.N; n++ {
+ runtime.IsPinned(unsafe.Pointer(ptr))
+ }
+}
+
+func BenchmarkPinnerIsPinnedOnPinnedParallel(b *testing.B) {
+ var pinner runtime.Pinner
+ ptr := new(obj)
+ pinner.Pin(ptr)
+ b.ResetTimer()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ runtime.IsPinned(unsafe.Pointer(ptr))
+ }
+ })
+ pinner.Unpin()
+}
+
+func BenchmarkPinnerIsPinnedOnUnpinnedParallel(b *testing.B) {
+ ptr := new(obj)
+ b.ResetTimer()
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ runtime.IsPinned(unsafe.Pointer(ptr))
+ }
+ })
+}
+
+// const string data is not in span.
+func TestPinnerConstStringData(t *testing.T) {
+ var pinner runtime.Pinner
+ str := "test-const-string"
+ p := unsafe.StringData(str)
+ addr := unsafe.Pointer(p)
+ if !runtime.IsPinned(addr) {
+ t.Fatal("not marked as pinned")
+ }
+ pinner.Pin(p)
+ pinner.Unpin()
+ if !runtime.IsPinned(addr) {
+ t.Fatal("not marked as pinned")
+ }
+}