| // 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") |
| } |
| } |