Source file
src/runtime/arena_test.go
1
2
3
4
5 package runtime_test
6
7 import (
8 "internal/goarch"
9 "internal/runtime/atomic"
10 "reflect"
11 . "runtime"
12 "runtime/debug"
13 "testing"
14 "time"
15 "unsafe"
16 )
17
18 type smallScalar struct {
19 X uintptr
20 }
21 type smallPointer struct {
22 X *smallPointer
23 }
24 type smallPointerMix struct {
25 A *smallPointer
26 B byte
27 C *smallPointer
28 D [11]byte
29 }
30 type mediumScalarEven [8192]byte
31 type mediumScalarOdd [3321]byte
32 type mediumPointerEven [1024]*smallPointer
33 type mediumPointerOdd [1023]*smallPointer
34
35 type largeScalar [UserArenaChunkBytes + 1]byte
36 type largePointer [UserArenaChunkBytes/unsafe.Sizeof(&smallPointer{}) + 1]*smallPointer
37
38 func TestUserArena(t *testing.T) {
39
40
41 defer GOMAXPROCS(GOMAXPROCS(2))
42
43
44 t.Run("Alloc", func(t *testing.T) {
45 ss := &smallScalar{5}
46 runSubTestUserArenaNew(t, ss, true)
47
48 sp := &smallPointer{new(smallPointer)}
49 runSubTestUserArenaNew(t, sp, true)
50
51 spm := &smallPointerMix{sp, 5, nil, [11]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}}
52 runSubTestUserArenaNew(t, spm, true)
53
54 mse := new(mediumScalarEven)
55 for i := range mse {
56 mse[i] = 121
57 }
58 runSubTestUserArenaNew(t, mse, true)
59
60 mso := new(mediumScalarOdd)
61 for i := range mso {
62 mso[i] = 122
63 }
64 runSubTestUserArenaNew(t, mso, true)
65
66 mpe := new(mediumPointerEven)
67 for i := range mpe {
68 mpe[i] = sp
69 }
70 runSubTestUserArenaNew(t, mpe, true)
71
72 mpo := new(mediumPointerOdd)
73 for i := range mpo {
74 mpo[i] = sp
75 }
76 runSubTestUserArenaNew(t, mpo, true)
77
78 ls := new(largeScalar)
79 for i := range ls {
80 ls[i] = 123
81 }
82
83 runSubTestUserArenaNew(t, ls, false)
84
85 lp := new(largePointer)
86 for i := range lp {
87 lp[i] = sp
88 }
89
90 runSubTestUserArenaNew(t, lp, false)
91
92 sss := make([]smallScalar, 25)
93 for i := range sss {
94 sss[i] = smallScalar{12}
95 }
96 runSubTestUserArenaSlice(t, sss, true)
97
98 mpos := make([]mediumPointerOdd, 5)
99 for i := range mpos {
100 mpos[i] = *mpo
101 }
102 runSubTestUserArenaSlice(t, mpos, true)
103
104 sps := make([]smallPointer, UserArenaChunkBytes/unsafe.Sizeof(smallPointer{})+1)
105 for i := range sps {
106 sps[i] = *sp
107 }
108
109 runSubTestUserArenaSlice(t, sps, false)
110
111
112 t.Run("struct{}", func(t *testing.T) {
113 arena := NewUserArena()
114 var x any
115 x = (*struct{})(nil)
116 arena.New(&x)
117 if v := unsafe.Pointer(x.(*struct{})); v != ZeroBase {
118 t.Errorf("expected zero-sized type to be allocated as zerobase: got %x, want %x", v, ZeroBase)
119 }
120 arena.Free()
121 })
122 t.Run("[]struct{}", func(t *testing.T) {
123 arena := NewUserArena()
124 var sl []struct{}
125 arena.Slice(&sl, 10)
126 if v := unsafe.Pointer(&sl[0]); v != ZeroBase {
127 t.Errorf("expected zero-sized type to be allocated as zerobase: got %x, want %x", v, ZeroBase)
128 }
129 arena.Free()
130 })
131 t.Run("[]int (cap 0)", func(t *testing.T) {
132 arena := NewUserArena()
133 var sl []int
134 arena.Slice(&sl, 0)
135 if len(sl) != 0 {
136 t.Errorf("expected requested zero-sized slice to still have zero length: got %x, want 0", len(sl))
137 }
138 arena.Free()
139 })
140 })
141
142
143 GC()
144
145 if n := GlobalWaitingArenaChunks(); n != 0 {
146 t.Errorf("expected zero waiting arena chunks, found %d", n)
147 }
148 }
149
150 func runSubTestUserArenaNew[S comparable](t *testing.T, value *S, parallel bool) {
151 t.Run(reflect.TypeOf(value).Elem().Name(), func(t *testing.T) {
152 if parallel {
153 t.Parallel()
154 }
155
156
157
158
159
160 n := int(UserArenaChunkBytes / unsafe.Sizeof(*value))
161 if n == 0 {
162 n = 1
163 }
164
165
166 arena := NewUserArena()
167
168 arenaValues := make([]*S, 0, n)
169 for j := 0; j < n; j++ {
170 var x any
171 x = (*S)(nil)
172 arena.New(&x)
173 s := x.(*S)
174 *s = *value
175 arenaValues = append(arenaValues, s)
176 }
177
178 for _, s := range arenaValues {
179 if *s != *value {
180 t.Errorf("failed integrity check: got %#v, want %#v", *s, *value)
181 }
182 }
183
184
185 arena.Free()
186 })
187 }
188
189 func runSubTestUserArenaSlice[S comparable](t *testing.T, value []S, parallel bool) {
190 t.Run("[]"+reflect.TypeOf(value).Elem().Name(), func(t *testing.T) {
191 if parallel {
192 t.Parallel()
193 }
194
195
196
197
198
199 n := int(UserArenaChunkBytes / (unsafe.Sizeof(*new(S)) * uintptr(cap(value))))
200 if n == 0 {
201 n = 1
202 }
203
204
205 arena := NewUserArena()
206
207 arenaValues := make([][]S, 0, n)
208 for j := 0; j < n; j++ {
209 var sl []S
210 arena.Slice(&sl, cap(value))
211 copy(sl, value)
212 arenaValues = append(arenaValues, sl)
213 }
214
215 for _, sl := range arenaValues {
216 for i := range sl {
217 got := sl[i]
218 want := value[i]
219 if got != want {
220 t.Errorf("failed integrity check: got %#v, want %#v at index %d", got, want, i)
221 }
222 }
223 }
224
225
226 arena.Free()
227 })
228 }
229
230 func TestUserArenaLiveness(t *testing.T) {
231 t.Run("Free", func(t *testing.T) {
232 testUserArenaLiveness(t, false)
233 })
234 t.Run("Finalizer", func(t *testing.T) {
235 testUserArenaLiveness(t, true)
236 })
237 }
238
239 func testUserArenaLiveness(t *testing.T, useArenaFinalizer bool) {
240
241
242 defer debug.SetGCPercent(debug.SetGCPercent(-1))
243
244
245 GC()
246 GC()
247
248 arena := NewUserArena()
249
250
251
252 for i := 0; i < 3; i++ {
253 var x any
254 x = (*mediumPointerOdd)(nil)
255 arena.New(&x)
256 }
257
258 var x any
259 x = (*smallPointerMix)(nil)
260 arena.New(&x)
261 v := x.(*smallPointerMix)
262
263 var safeToFinalize atomic.Bool
264 var finalized atomic.Bool
265 v.C = new(smallPointer)
266 SetFinalizer(v.C, func(_ *smallPointer) {
267 if !safeToFinalize.Load() {
268 t.Error("finalized arena-referenced object unexpectedly")
269 }
270 finalized.Store(true)
271 })
272
273
274 GC()
275 GC()
276
277
278
279
280 for i := 0; i < int(UserArenaChunkBytes/unsafe.Sizeof(mediumScalarEven{})); i++ {
281 var x any
282 x = (*mediumScalarEven)(nil)
283 arena.New(&x)
284 }
285
286
287 GC()
288 GC()
289
290 v = nil
291
292 safeToFinalize.Store(true)
293 if useArenaFinalizer {
294 arena = nil
295
296
297 GC()
298 GC()
299
300
301
302 if !BlockUntilEmptyFinalizerQueue(int64(2 * time.Second)) {
303 t.Fatal("finalizer queue was never emptied")
304 }
305 } else {
306
307 arena.Free()
308 }
309
310
311 GC()
312 GC()
313
314 if !BlockUntilEmptyFinalizerQueue(int64(2 * time.Second)) {
315 t.Fatal("finalizer queue was never emptied")
316 }
317 if !finalized.Load() {
318 t.Error("expected arena-referenced object to be finalized")
319 }
320 }
321
322 func TestUserArenaClearsPointerBits(t *testing.T) {
323
324
325
326
327
328 x := new([8 << 20]byte)
329 xp := uintptr(unsafe.Pointer(&x[124]))
330 var finalized atomic.Bool
331 SetFinalizer(x, func(_ *[8 << 20]byte) {
332 finalized.Store(true)
333 })
334
335
336
337
338 a := NewUserArena()
339 for i := 0; i < int(UserArenaChunkBytes/goarch.PtrSize*3); i++ {
340 var x any
341 x = (*smallPointer)(nil)
342 a.New(&x)
343 }
344 a.Free()
345
346
347 GC()
348 GC()
349
350 a = NewUserArena()
351 for i := 0; i < int(UserArenaChunkBytes/goarch.PtrSize*2); i++ {
352 var x any
353 x = (*smallScalar)(nil)
354 a.New(&x)
355 v := x.(*smallScalar)
356
357 *v = smallScalar{xp}
358 }
359 KeepAlive(x)
360 x = nil
361
362
363 GC()
364 GC()
365
366 if !BlockUntilEmptyFinalizerQueue(int64(2 * time.Second)) {
367 t.Fatal("finalizer queue was never emptied")
368 }
369 if !finalized.Load() {
370 t.Fatal("heap allocation kept alive through non-pointer reference")
371 }
372
373
374 a.Free()
375 GC()
376 GC()
377 }
378
379 func TestUserArenaCloneString(t *testing.T) {
380 a := NewUserArena()
381
382
383 var s = "abcdefghij"
384
385
386 var b []byte
387 a.Slice(&b, len(s))
388 copy(b, s)
389
390
391
392
393 as := unsafe.String(&b[0], len(b))
394
395
396 asCopy := UserArenaClone(as)
397 if unsafe.StringData(as) == unsafe.StringData(asCopy) {
398 t.Error("Clone did not make a copy")
399 }
400
401
402 subAs := as[1:3]
403 subAsCopy := UserArenaClone(subAs)
404 if unsafe.StringData(subAs) == unsafe.StringData(subAsCopy) {
405 t.Error("Clone did not make a copy")
406 }
407 if len(subAs) != len(subAsCopy) {
408 t.Errorf("Clone made an incorrect copy (bad length): %d -> %d", len(subAs), len(subAsCopy))
409 } else {
410 for i := range subAs {
411 if subAs[i] != subAsCopy[i] {
412 t.Errorf("Clone made an incorrect copy (data at index %d): %d -> %d", i, subAs[i], subAs[i])
413 }
414 }
415 }
416
417
418 doubleAs := as + as
419 doubleAsCopy := UserArenaClone(doubleAs)
420 if unsafe.StringData(doubleAs) != unsafe.StringData(doubleAsCopy) {
421 t.Error("Clone should not have made a copy")
422 }
423
424
425 sCopy := UserArenaClone(s)
426 if unsafe.StringData(s) != unsafe.StringData(sCopy) {
427 t.Error("Clone should not have made a copy")
428 }
429
430 a.Free()
431 }
432
433 func TestUserArenaClonePointer(t *testing.T) {
434 a := NewUserArena()
435
436
437 x := Escape(new(smallScalar))
438 xCopy := UserArenaClone(x)
439 if unsafe.Pointer(x) != unsafe.Pointer(xCopy) {
440 t.Errorf("Clone should not have made a copy: %#v -> %#v", x, xCopy)
441 }
442
443
444 var i any
445 i = (*smallScalar)(nil)
446 a.New(&i)
447 xArena := i.(*smallScalar)
448 xArenaCopy := UserArenaClone(xArena)
449 if unsafe.Pointer(xArena) == unsafe.Pointer(xArenaCopy) {
450 t.Errorf("Clone should have made a copy: %#v -> %#v", xArena, xArenaCopy)
451 }
452 if *xArena != *xArenaCopy {
453 t.Errorf("Clone made an incorrect copy copy: %#v -> %#v", *xArena, *xArenaCopy)
454 }
455
456 a.Free()
457 }
458
459 func TestUserArenaCloneSlice(t *testing.T) {
460 a := NewUserArena()
461
462
463 var s = "klmnopqrstuv"
464
465
466 var b []byte
467 a.Slice(&b, len(s))
468 copy(b, s)
469
470
471 bCopy := UserArenaClone(b)
472 if unsafe.Pointer(&b[0]) == unsafe.Pointer(&bCopy[0]) {
473 t.Errorf("Clone did not make a copy: %#v -> %#v", b, bCopy)
474 }
475 if len(b) != len(bCopy) {
476 t.Errorf("Clone made an incorrect copy (bad length): %d -> %d", len(b), len(bCopy))
477 } else {
478 for i := range b {
479 if b[i] != bCopy[i] {
480 t.Errorf("Clone made an incorrect copy (data at index %d): %d -> %d", i, b[i], bCopy[i])
481 }
482 }
483 }
484
485
486 bSub := b[1:3]
487 bSubCopy := UserArenaClone(bSub)
488 if unsafe.Pointer(&bSub[0]) == unsafe.Pointer(&bSubCopy[0]) {
489 t.Errorf("Clone did not make a copy: %#v -> %#v", bSub, bSubCopy)
490 }
491 if len(bSub) != len(bSubCopy) {
492 t.Errorf("Clone made an incorrect copy (bad length): %d -> %d", len(bSub), len(bSubCopy))
493 } else {
494 for i := range bSub {
495 if bSub[i] != bSubCopy[i] {
496 t.Errorf("Clone made an incorrect copy (data at index %d): %d -> %d", i, bSub[i], bSubCopy[i])
497 }
498 }
499 }
500
501
502 bNotArena := make([]byte, len(s))
503 copy(bNotArena, s)
504 bNotArenaCopy := UserArenaClone(bNotArena)
505 if unsafe.Pointer(&bNotArena[0]) != unsafe.Pointer(&bNotArenaCopy[0]) {
506 t.Error("Clone should not have made a copy")
507 }
508
509 a.Free()
510 }
511
512 func TestUserArenaClonePanic(t *testing.T) {
513 var s string
514 func() {
515 x := smallScalar{2}
516 defer func() {
517 if v := recover(); v != nil {
518 s = v.(string)
519 }
520 }()
521 UserArenaClone(x)
522 }()
523 if s == "" {
524 t.Errorf("expected panic from Clone")
525 }
526 }
527
View as plain text