Source file src/runtime/pinner_test.go

     1  // Copyright 2023 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package runtime_test
     6  
     7  import (
     8  	"runtime"
     9  	"testing"
    10  	"time"
    11  	"unsafe"
    12  )
    13  
    14  type obj struct {
    15  	x int64
    16  	y int64
    17  	z int64
    18  }
    19  
    20  type objWith[T any] struct {
    21  	x int64
    22  	y int64
    23  	z int64
    24  	o T
    25  }
    26  
    27  var (
    28  	globalUintptr                uintptr
    29  	globalPtrToObj               = &obj{}
    30  	globalPtrToObjWithPtr        = &objWith[*uintptr]{}
    31  	globalPtrToRuntimeObj        = func() *obj { return &obj{} }()
    32  	globalPtrToRuntimeObjWithPtr = func() *objWith[*uintptr] { return &objWith[*uintptr]{} }()
    33  )
    34  
    35  func assertDidPanic(t *testing.T) {
    36  	if recover() == nil {
    37  		t.Fatal("did not panic")
    38  	}
    39  }
    40  
    41  func assertCgoCheckPanics(t *testing.T, p any) {
    42  	defer func() {
    43  		if recover() == nil {
    44  			t.Fatal("cgoCheckPointer() did not panic, make sure the tests run with cgocheck=1")
    45  		}
    46  	}()
    47  	runtime.CgoCheckPointer(p, true)
    48  }
    49  
    50  func TestPinnerSimple(t *testing.T) {
    51  	var pinner runtime.Pinner
    52  	p := new(obj)
    53  	addr := unsafe.Pointer(p)
    54  	if runtime.IsPinned(addr) {
    55  		t.Fatal("already marked as pinned")
    56  	}
    57  	pinner.Pin(p)
    58  	if !runtime.IsPinned(addr) {
    59  		t.Fatal("not marked as pinned")
    60  	}
    61  	if runtime.GetPinCounter(addr) != nil {
    62  		t.Fatal("pin counter should not exist")
    63  	}
    64  	pinner.Unpin()
    65  	if runtime.IsPinned(addr) {
    66  		t.Fatal("still marked as pinned")
    67  	}
    68  }
    69  
    70  func TestPinnerPinKeepsAliveAndReleases(t *testing.T) {
    71  	var pinner runtime.Pinner
    72  	p := new(obj)
    73  	done := make(chan struct{})
    74  	runtime.SetFinalizer(p, func(any) {
    75  		done <- struct{}{}
    76  	})
    77  	pinner.Pin(p)
    78  	p = nil
    79  	runtime.GC()
    80  	runtime.GC()
    81  	select {
    82  	case <-done:
    83  		t.Fatal("Pin() didn't keep object alive")
    84  	case <-time.After(time.Millisecond * 10):
    85  		break
    86  	}
    87  	pinner.Unpin()
    88  	runtime.GC()
    89  	runtime.GC()
    90  	select {
    91  	case <-done:
    92  		break
    93  	case <-time.After(time.Second):
    94  		t.Fatal("Unpin() didn't release object")
    95  	}
    96  }
    97  
    98  func TestPinnerMultiplePinsSame(t *testing.T) {
    99  	const N = 100
   100  	var pinner runtime.Pinner
   101  	p := new(obj)
   102  	addr := unsafe.Pointer(p)
   103  	if runtime.IsPinned(addr) {
   104  		t.Fatal("already marked as pinned")
   105  	}
   106  	for i := 0; i < N; i++ {
   107  		pinner.Pin(p)
   108  	}
   109  	if !runtime.IsPinned(addr) {
   110  		t.Fatal("not marked as pinned")
   111  	}
   112  	if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != N-1 {
   113  		t.Fatalf("pin counter incorrect: %d", *cnt)
   114  	}
   115  	pinner.Unpin()
   116  	if runtime.IsPinned(addr) {
   117  		t.Fatal("still marked as pinned")
   118  	}
   119  	if runtime.GetPinCounter(addr) != nil {
   120  		t.Fatal("pin counter was not deleted")
   121  	}
   122  }
   123  
   124  func TestPinnerTwoPinner(t *testing.T) {
   125  	var pinner1, pinner2 runtime.Pinner
   126  	p := new(obj)
   127  	addr := unsafe.Pointer(p)
   128  	if runtime.IsPinned(addr) {
   129  		t.Fatal("already marked as pinned")
   130  	}
   131  	pinner1.Pin(p)
   132  	if !runtime.IsPinned(addr) {
   133  		t.Fatal("not marked as pinned")
   134  	}
   135  	if runtime.GetPinCounter(addr) != nil {
   136  		t.Fatal("pin counter should not exist")
   137  	}
   138  	pinner2.Pin(p)
   139  	if !runtime.IsPinned(addr) {
   140  		t.Fatal("not marked as pinned")
   141  	}
   142  	if cnt := runtime.GetPinCounter(addr); cnt == nil || *cnt != 1 {
   143  		t.Fatalf("pin counter incorrect: %d", *cnt)
   144  	}
   145  	pinner1.Unpin()
   146  	if !runtime.IsPinned(addr) {
   147  		t.Fatal("not marked as pinned")
   148  	}
   149  	if runtime.GetPinCounter(addr) != nil {
   150  		t.Fatal("pin counter should not exist")
   151  	}
   152  	pinner2.Unpin()
   153  	if runtime.IsPinned(addr) {
   154  		t.Fatal("still marked as pinned")
   155  	}
   156  	if runtime.GetPinCounter(addr) != nil {
   157  		t.Fatal("pin counter was not deleted")
   158  	}
   159  }
   160  
   161  func TestPinnerPinZerosizeObj(t *testing.T) {
   162  	var pinner runtime.Pinner
   163  	defer pinner.Unpin()
   164  	p := new(struct{})
   165  	pinner.Pin(p)
   166  	if !runtime.IsPinned(unsafe.Pointer(p)) {
   167  		t.Fatal("not marked as pinned")
   168  	}
   169  }
   170  
   171  func TestPinnerPinGlobalPtr(t *testing.T) {
   172  	var pinner runtime.Pinner
   173  	defer pinner.Unpin()
   174  	pinner.Pin(globalPtrToObj)
   175  	pinner.Pin(globalPtrToObjWithPtr)
   176  	pinner.Pin(globalPtrToRuntimeObj)
   177  	pinner.Pin(globalPtrToRuntimeObjWithPtr)
   178  }
   179  
   180  func TestPinnerPinTinyObj(t *testing.T) {
   181  	var pinner runtime.Pinner
   182  	const N = 64
   183  	var addr [N]unsafe.Pointer
   184  	for i := 0; i < N; i++ {
   185  		p := new(bool)
   186  		addr[i] = unsafe.Pointer(p)
   187  		pinner.Pin(p)
   188  		pinner.Pin(p)
   189  		if !runtime.IsPinned(addr[i]) {
   190  			t.Fatalf("not marked as pinned: %d", i)
   191  		}
   192  		if cnt := runtime.GetPinCounter(addr[i]); cnt == nil || *cnt == 0 {
   193  			t.Fatalf("pin counter incorrect: %d, %d", *cnt, i)
   194  		}
   195  	}
   196  	pinner.Unpin()
   197  	for i := 0; i < N; i++ {
   198  		if runtime.IsPinned(addr[i]) {
   199  			t.Fatal("still marked as pinned")
   200  		}
   201  		if runtime.GetPinCounter(addr[i]) != nil {
   202  			t.Fatal("pin counter should not exist")
   203  		}
   204  	}
   205  }
   206  
   207  func TestPinnerInterface(t *testing.T) {
   208  	var pinner runtime.Pinner
   209  	o := new(obj)
   210  	ifc := any(o)
   211  	pinner.Pin(&ifc)
   212  	if !runtime.IsPinned(unsafe.Pointer(&ifc)) {
   213  		t.Fatal("not marked as pinned")
   214  	}
   215  	if runtime.IsPinned(unsafe.Pointer(o)) {
   216  		t.Fatal("marked as pinned")
   217  	}
   218  	pinner.Unpin()
   219  	pinner.Pin(ifc)
   220  	if !runtime.IsPinned(unsafe.Pointer(o)) {
   221  		t.Fatal("not marked as pinned")
   222  	}
   223  	if runtime.IsPinned(unsafe.Pointer(&ifc)) {
   224  		t.Fatal("marked as pinned")
   225  	}
   226  	pinner.Unpin()
   227  }
   228  
   229  func TestPinnerPinNonPtrPanics(t *testing.T) {
   230  	var pinner runtime.Pinner
   231  	defer pinner.Unpin()
   232  	var i int
   233  	defer assertDidPanic(t)
   234  	pinner.Pin(i)
   235  }
   236  
   237  func TestPinnerReuse(t *testing.T) {
   238  	var pinner runtime.Pinner
   239  	p := new(obj)
   240  	p2 := &p
   241  	assertCgoCheckPanics(t, p2)
   242  	pinner.Pin(p)
   243  	runtime.CgoCheckPointer(p2, true)
   244  	pinner.Unpin()
   245  	assertCgoCheckPanics(t, p2)
   246  	pinner.Pin(p)
   247  	runtime.CgoCheckPointer(p2, true)
   248  	pinner.Unpin()
   249  }
   250  
   251  func TestPinnerEmptyUnpin(t *testing.T) {
   252  	var pinner runtime.Pinner
   253  	pinner.Unpin()
   254  	pinner.Unpin()
   255  }
   256  
   257  func TestPinnerLeakPanics(t *testing.T) {
   258  	old := runtime.GetPinnerLeakPanic()
   259  	func() {
   260  		defer assertDidPanic(t)
   261  		old()
   262  	}()
   263  	done := make(chan struct{})
   264  	runtime.SetPinnerLeakPanic(func() {
   265  		done <- struct{}{}
   266  	})
   267  	func() {
   268  		var pinner runtime.Pinner
   269  		p := new(obj)
   270  		pinner.Pin(p)
   271  	}()
   272  	runtime.GC()
   273  	runtime.GC()
   274  	select {
   275  	case <-done:
   276  		break
   277  	case <-time.After(time.Second):
   278  		t.Fatal("leak didn't make GC to panic")
   279  	}
   280  	runtime.SetPinnerLeakPanic(old)
   281  }
   282  
   283  func TestPinnerCgoCheckPtr2Ptr(t *testing.T) {
   284  	var pinner runtime.Pinner
   285  	defer pinner.Unpin()
   286  	p := new(obj)
   287  	p2 := &objWith[*obj]{o: p}
   288  	assertCgoCheckPanics(t, p2)
   289  	pinner.Pin(p)
   290  	runtime.CgoCheckPointer(p2, true)
   291  }
   292  
   293  func TestPinnerCgoCheckPtr2UnsafePtr(t *testing.T) {
   294  	var pinner runtime.Pinner
   295  	defer pinner.Unpin()
   296  	p := unsafe.Pointer(new(obj))
   297  	p2 := &objWith[unsafe.Pointer]{o: p}
   298  	assertCgoCheckPanics(t, p2)
   299  	pinner.Pin(p)
   300  	runtime.CgoCheckPointer(p2, true)
   301  }
   302  
   303  func TestPinnerCgoCheckPtr2UnknownPtr(t *testing.T) {
   304  	var pinner runtime.Pinner
   305  	defer pinner.Unpin()
   306  	p := unsafe.Pointer(new(obj))
   307  	p2 := &p
   308  	func() {
   309  		defer assertDidPanic(t)
   310  		runtime.CgoCheckPointer(p2, nil)
   311  	}()
   312  	pinner.Pin(p)
   313  	runtime.CgoCheckPointer(p2, nil)
   314  }
   315  
   316  func TestPinnerCgoCheckInterface(t *testing.T) {
   317  	var pinner runtime.Pinner
   318  	defer pinner.Unpin()
   319  	var ifc any
   320  	var o obj
   321  	ifc = &o
   322  	p := &ifc
   323  	assertCgoCheckPanics(t, p)
   324  	pinner.Pin(&o)
   325  	runtime.CgoCheckPointer(p, true)
   326  }
   327  
   328  func TestPinnerCgoCheckSlice(t *testing.T) {
   329  	var pinner runtime.Pinner
   330  	defer pinner.Unpin()
   331  	sl := []int{1, 2, 3}
   332  	assertCgoCheckPanics(t, &sl)
   333  	pinner.Pin(&sl[0])
   334  	runtime.CgoCheckPointer(&sl, true)
   335  }
   336  
   337  func TestPinnerCgoCheckString(t *testing.T) {
   338  	var pinner runtime.Pinner
   339  	defer pinner.Unpin()
   340  	b := []byte("foobar")
   341  	str := unsafe.String(&b[0], 6)
   342  	assertCgoCheckPanics(t, &str)
   343  	pinner.Pin(&b[0])
   344  	runtime.CgoCheckPointer(&str, true)
   345  }
   346  
   347  func TestPinnerCgoCheckPinned2UnpinnedPanics(t *testing.T) {
   348  	var pinner runtime.Pinner
   349  	defer pinner.Unpin()
   350  	p := new(obj)
   351  	p2 := &objWith[*obj]{o: p}
   352  	assertCgoCheckPanics(t, p2)
   353  	pinner.Pin(p2)
   354  	assertCgoCheckPanics(t, p2)
   355  }
   356  
   357  func TestPinnerCgoCheckPtr2Pinned2Unpinned(t *testing.T) {
   358  	var pinner runtime.Pinner
   359  	defer pinner.Unpin()
   360  	p := new(obj)
   361  	p2 := &objWith[*obj]{o: p}
   362  	p3 := &objWith[*objWith[*obj]]{o: p2}
   363  	assertCgoCheckPanics(t, p2)
   364  	assertCgoCheckPanics(t, p3)
   365  	pinner.Pin(p2)
   366  	assertCgoCheckPanics(t, p2)
   367  	assertCgoCheckPanics(t, p3)
   368  	pinner.Pin(p)
   369  	runtime.CgoCheckPointer(p2, true)
   370  	runtime.CgoCheckPointer(p3, true)
   371  }
   372  
   373  func BenchmarkPinnerPinUnpinBatch(b *testing.B) {
   374  	const Batch = 1000
   375  	var data [Batch]*obj
   376  	for i := 0; i < Batch; i++ {
   377  		data[i] = new(obj)
   378  	}
   379  	b.ResetTimer()
   380  	for n := 0; n < b.N; n++ {
   381  		var pinner runtime.Pinner
   382  		for i := 0; i < Batch; i++ {
   383  			pinner.Pin(data[i])
   384  		}
   385  		pinner.Unpin()
   386  	}
   387  }
   388  
   389  func BenchmarkPinnerPinUnpinBatchDouble(b *testing.B) {
   390  	const Batch = 1000
   391  	var data [Batch]*obj
   392  	for i := 0; i < Batch; i++ {
   393  		data[i] = new(obj)
   394  	}
   395  	b.ResetTimer()
   396  	for n := 0; n < b.N; n++ {
   397  		var pinner runtime.Pinner
   398  		for i := 0; i < Batch; i++ {
   399  			pinner.Pin(data[i])
   400  			pinner.Pin(data[i])
   401  		}
   402  		pinner.Unpin()
   403  	}
   404  }
   405  
   406  func BenchmarkPinnerPinUnpinBatchTiny(b *testing.B) {
   407  	const Batch = 1000
   408  	var data [Batch]*bool
   409  	for i := 0; i < Batch; i++ {
   410  		data[i] = new(bool)
   411  	}
   412  	b.ResetTimer()
   413  	for n := 0; n < b.N; n++ {
   414  		var pinner runtime.Pinner
   415  		for i := 0; i < Batch; i++ {
   416  			pinner.Pin(data[i])
   417  		}
   418  		pinner.Unpin()
   419  	}
   420  }
   421  
   422  func BenchmarkPinnerPinUnpin(b *testing.B) {
   423  	p := new(obj)
   424  	for n := 0; n < b.N; n++ {
   425  		var pinner runtime.Pinner
   426  		pinner.Pin(p)
   427  		pinner.Unpin()
   428  	}
   429  }
   430  
   431  func BenchmarkPinnerPinUnpinTiny(b *testing.B) {
   432  	p := new(bool)
   433  	for n := 0; n < b.N; n++ {
   434  		var pinner runtime.Pinner
   435  		pinner.Pin(p)
   436  		pinner.Unpin()
   437  	}
   438  }
   439  
   440  func BenchmarkPinnerPinUnpinDouble(b *testing.B) {
   441  	p := new(obj)
   442  	for n := 0; n < b.N; n++ {
   443  		var pinner runtime.Pinner
   444  		pinner.Pin(p)
   445  		pinner.Pin(p)
   446  		pinner.Unpin()
   447  	}
   448  }
   449  
   450  func BenchmarkPinnerPinUnpinParallel(b *testing.B) {
   451  	b.RunParallel(func(pb *testing.PB) {
   452  		p := new(obj)
   453  		for pb.Next() {
   454  			var pinner runtime.Pinner
   455  			pinner.Pin(p)
   456  			pinner.Unpin()
   457  		}
   458  	})
   459  }
   460  
   461  func BenchmarkPinnerPinUnpinParallelTiny(b *testing.B) {
   462  	b.RunParallel(func(pb *testing.PB) {
   463  		p := new(bool)
   464  		for pb.Next() {
   465  			var pinner runtime.Pinner
   466  			pinner.Pin(p)
   467  			pinner.Unpin()
   468  		}
   469  	})
   470  }
   471  
   472  func BenchmarkPinnerPinUnpinParallelDouble(b *testing.B) {
   473  	b.RunParallel(func(pb *testing.PB) {
   474  		p := new(obj)
   475  		for pb.Next() {
   476  			var pinner runtime.Pinner
   477  			pinner.Pin(p)
   478  			pinner.Pin(p)
   479  			pinner.Unpin()
   480  		}
   481  	})
   482  }
   483  
   484  func BenchmarkPinnerIsPinnedOnPinned(b *testing.B) {
   485  	var pinner runtime.Pinner
   486  	ptr := new(obj)
   487  	pinner.Pin(ptr)
   488  	b.ResetTimer()
   489  	for n := 0; n < b.N; n++ {
   490  		runtime.IsPinned(unsafe.Pointer(ptr))
   491  	}
   492  	pinner.Unpin()
   493  }
   494  
   495  func BenchmarkPinnerIsPinnedOnUnpinned(b *testing.B) {
   496  	ptr := new(obj)
   497  	b.ResetTimer()
   498  	for n := 0; n < b.N; n++ {
   499  		runtime.IsPinned(unsafe.Pointer(ptr))
   500  	}
   501  }
   502  
   503  func BenchmarkPinnerIsPinnedOnPinnedParallel(b *testing.B) {
   504  	var pinner runtime.Pinner
   505  	ptr := new(obj)
   506  	pinner.Pin(ptr)
   507  	b.ResetTimer()
   508  	b.RunParallel(func(pb *testing.PB) {
   509  		for pb.Next() {
   510  			runtime.IsPinned(unsafe.Pointer(ptr))
   511  		}
   512  	})
   513  	pinner.Unpin()
   514  }
   515  
   516  func BenchmarkPinnerIsPinnedOnUnpinnedParallel(b *testing.B) {
   517  	ptr := new(obj)
   518  	b.ResetTimer()
   519  	b.RunParallel(func(pb *testing.PB) {
   520  		for pb.Next() {
   521  			runtime.IsPinned(unsafe.Pointer(ptr))
   522  		}
   523  	})
   524  }
   525  
   526  // const string data is not in span.
   527  func TestPinnerConstStringData(t *testing.T) {
   528  	var pinner runtime.Pinner
   529  	str := "test-const-string"
   530  	p := unsafe.StringData(str)
   531  	addr := unsafe.Pointer(p)
   532  	if !runtime.IsPinned(addr) {
   533  		t.Fatal("not marked as pinned")
   534  	}
   535  	pinner.Pin(p)
   536  	pinner.Unpin()
   537  	if !runtime.IsPinned(addr) {
   538  		t.Fatal("not marked as pinned")
   539  	}
   540  }
   541  

View as plain text