blob: ef8500cd3518b13785d65a1699f90df017e25645 [file] [log] [blame]
// 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")
}
}