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

View as plain text