-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpointer_test.go
329 lines (300 loc) · 8.55 KB
/
pointer_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
// Copyright 2024 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 weak_test
import (
"context"
"internal/goarch"
"runtime"
"sync"
"testing"
"time"
"unsafe"
"weak"
)
type T struct {
// N.B. This must contain a pointer, otherwise the weak handle might get placed
// in a tiny block making the tests in this package flaky.
t *T
a int
b int
}
func TestPointer(t *testing.T) {
var zero weak.Pointer[T]
if zero.Value() != nil {
t.Error("Value of zero value of weak.Pointer is not nil")
}
zeroNil := weak.Make[T](nil)
if zeroNil.Value() != nil {
t.Error("Value of weak.Make[T](nil) is not nil")
}
bt := new(T)
wt := weak.Make(bt)
if st := wt.Value(); st != bt {
t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt)
}
// bt is still referenced.
runtime.GC()
if st := wt.Value(); st != bt {
t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt)
}
// bt is no longer referenced.
runtime.GC()
if st := wt.Value(); st != nil {
t.Fatalf("expected weak pointer to be nil, got %p", st)
}
}
func TestPointerEquality(t *testing.T) {
var zero weak.Pointer[T]
zeroNil := weak.Make[T](nil)
if zero != zeroNil {
t.Error("weak.Make[T](nil) != zero value of weak.Pointer[T]")
}
bt := make([]*T, 10)
wt := make([]weak.Pointer[T], 10)
wo := make([]weak.Pointer[int], 10)
for i := range bt {
bt[i] = new(T)
wt[i] = weak.Make(bt[i])
wo[i] = weak.Make(&bt[i].a)
}
for i := range bt {
st := wt[i].Value()
if st != bt[i] {
t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
}
if wp := weak.Make(st); wp != wt[i] {
t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
}
if wp := weak.Make(&st.a); wp != wo[i] {
t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
}
if i == 0 {
continue
}
if wt[i] == wt[i-1] {
t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
}
}
// bt is still referenced.
runtime.GC()
for i := range bt {
st := wt[i].Value()
if st != bt[i] {
t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
}
if wp := weak.Make(st); wp != wt[i] {
t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
}
if wp := weak.Make(&st.a); wp != wo[i] {
t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
}
if i == 0 {
continue
}
if wt[i] == wt[i-1] {
t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
}
}
bt = nil
// bt is no longer referenced.
runtime.GC()
for i := range bt {
st := wt[i].Value()
if st != nil {
t.Fatalf("expected weak pointer to be nil, got %p", st)
}
if i == 0 {
continue
}
if wt[i] == wt[i-1] {
t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
}
}
}
func TestPointerFinalizer(t *testing.T) {
bt := new(T)
wt := weak.Make(bt)
done := make(chan struct{}, 1)
runtime.SetFinalizer(bt, func(bt *T) {
if wt.Value() != nil {
t.Errorf("weak pointer did not go nil before finalizer ran")
}
done <- struct{}{}
})
// Make sure the weak pointer stays around while bt is live.
runtime.GC()
if wt.Value() == nil {
t.Errorf("weak pointer went nil too soon")
}
runtime.KeepAlive(bt)
// bt is no longer referenced.
//
// Run one cycle to queue the finalizer.
runtime.GC()
if wt.Value() != nil {
t.Errorf("weak pointer did not go nil when finalizer was enqueued")
}
// Wait for the finalizer to run.
<-done
// The weak pointer should still be nil after the finalizer runs.
runtime.GC()
if wt.Value() != nil {
t.Errorf("weak pointer is non-nil even after finalization: %v", wt)
}
}
func TestPointerCleanup(t *testing.T) {
bt := new(T)
wt := weak.Make(bt)
done := make(chan struct{}, 1)
runtime.AddCleanup(bt, func(_ bool) {
if wt.Value() != nil {
t.Errorf("weak pointer did not go nil before cleanup was executed")
}
done <- struct{}{}
}, true)
// Make sure the weak pointer stays around while bt is live.
runtime.GC()
if wt.Value() == nil {
t.Errorf("weak pointer went nil too soon")
}
runtime.KeepAlive(bt)
// bt is no longer referenced.
//
// Run one cycle to queue the cleanup.
runtime.GC()
if wt.Value() != nil {
t.Errorf("weak pointer did not go nil when cleanup was enqueued")
}
// Wait for the cleanup to run.
<-done
// The weak pointer should still be nil after the cleanup runs.
runtime.GC()
if wt.Value() != nil {
t.Errorf("weak pointer is non-nil even after cleanup: %v", wt)
}
}
func TestPointerSize(t *testing.T) {
var p weak.Pointer[T]
size := unsafe.Sizeof(p)
if size != goarch.PtrSize {
t.Errorf("weak.Pointer[T] size = %d, want %d", size, goarch.PtrSize)
}
}
// Regression test for issue 69210.
//
// Weak-to-strong conversions must shade the new strong pointer, otherwise
// that might be creating the only strong pointer to a white object which
// is hidden in a blackened stack.
//
// Never fails if correct, fails with some high probability if incorrect.
func TestIssue69210(t *testing.T) {
if testing.Short() {
t.Skip("this is a stress test that takes seconds to run on its own")
}
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
// What we're trying to do is manufacture the conditions under which this
// bug happens. Specifically, we want:
//
// 1. To create a whole bunch of objects that are only weakly-pointed-to,
// 2. To call Value while the GC is in the mark phase,
// 3. The new strong pointer to be missed by the GC,
// 4. The following GC cycle to mark a free object.
//
// Unfortunately, (2) and (3) are hard to control, but we can increase
// the likelihood by having several goroutines do (1) at once while
// another goroutine constantly keeps us in the GC with runtime.GC.
// Like throwing darts at a dart board until they land just right.
// We can increase the likelihood of (4) by adding some delay after
// creating the strong pointer, but only if it's non-nil. If it's nil,
// that means it was already collected in which case there's no chance
// of triggering the bug, so we want to retry as fast as possible.
// Our heap here is tiny, so the GCs will go by fast.
//
// As of 2024-09-03, removing the line that shades pointers during
// the weak-to-strong conversion causes this test to fail about 50%
// of the time.
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for {
runtime.GC()
select {
case <-ctx.Done():
return
default:
}
}
}()
for range max(runtime.GOMAXPROCS(-1)-1, 1) {
wg.Add(1)
go func() {
defer wg.Done()
for {
for range 5 {
bt := new(T)
wt := weak.Make(bt)
bt = nil
time.Sleep(1 * time.Millisecond)
bt = wt.Value()
if bt != nil {
time.Sleep(4 * time.Millisecond)
bt.t = bt
bt.a = 12
}
runtime.KeepAlive(bt)
}
select {
case <-ctx.Done():
return
default:
}
}
}()
}
wg.Wait()
}
func TestIssue70739(t *testing.T) {
x := make([]*int, 4<<16)
wx1 := weak.Make(&x[1<<16])
wx2 := weak.Make(&x[1<<16])
if wx1 != wx2 {
t.Fatal("failed to look up special and made duplicate weak handle; see issue #70739")
}
}
var immortal T
func TestImmortalPointer(t *testing.T) {
w0 := weak.Make(&immortal)
if weak.Make(&immortal) != w0 {
t.Error("immortal weak pointers to the same pointer not equal")
}
w0a := weak.Make(&immortal.a)
w0b := weak.Make(&immortal.b)
if w0a == w0b {
t.Error("separate immortal pointers (same object) have the same pointer")
}
if got, want := w0.Value(), &immortal; got != want {
t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
}
if got, want := w0a.Value(), &immortal.a; got != want {
t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
}
if got, want := w0b.Value(), &immortal.b; got != want {
t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
}
// Run a couple of cycles.
runtime.GC()
runtime.GC()
// All immortal weak pointers should never get cleared.
if got, want := w0.Value(), &immortal; got != want {
t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
}
if got, want := w0a.Value(), &immortal.a; got != want {
t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
}
if got, want := w0b.Value(), &immortal.b; got != want {
t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
}
}