Source file
src/runtime/pinner_test.go
1
2
3
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
164
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
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