diff options
Diffstat (limited to 'src/runtime/pinner_test.go')
-rw-r--r-- | src/runtime/pinner_test.go | 540 |
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") + } +} |