1// Copyright 2024 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
5package weak_test
6
7import (
8	"context"
9	"internal/weak"
10	"runtime"
11	"sync"
12	"testing"
13	"time"
14)
15
16type T struct {
17	// N.B. This must contain a pointer, otherwise the weak handle might get placed
18	// in a tiny block making the tests in this package flaky.
19	t *T
20	a int
21}
22
23func TestPointer(t *testing.T) {
24	bt := new(T)
25	wt := weak.Make(bt)
26	if st := wt.Strong(); st != bt {
27		t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt)
28	}
29	// bt is still referenced.
30	runtime.GC()
31
32	if st := wt.Strong(); st != bt {
33		t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt)
34	}
35	// bt is no longer referenced.
36	runtime.GC()
37
38	if st := wt.Strong(); st != nil {
39		t.Fatalf("expected weak pointer to be nil, got %p", st)
40	}
41}
42
43func TestPointerEquality(t *testing.T) {
44	bt := make([]*T, 10)
45	wt := make([]weak.Pointer[T], 10)
46	for i := range bt {
47		bt[i] = new(T)
48		wt[i] = weak.Make(bt[i])
49	}
50	for i := range bt {
51		st := wt[i].Strong()
52		if st != bt[i] {
53			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
54		}
55		if wp := weak.Make(st); wp != wt[i] {
56			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
57		}
58		if i == 0 {
59			continue
60		}
61		if wt[i] == wt[i-1] {
62			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
63		}
64	}
65	// bt is still referenced.
66	runtime.GC()
67	for i := range bt {
68		st := wt[i].Strong()
69		if st != bt[i] {
70			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
71		}
72		if wp := weak.Make(st); wp != wt[i] {
73			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
74		}
75		if i == 0 {
76			continue
77		}
78		if wt[i] == wt[i-1] {
79			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
80		}
81	}
82	bt = nil
83	// bt is no longer referenced.
84	runtime.GC()
85	for i := range bt {
86		st := wt[i].Strong()
87		if st != nil {
88			t.Fatalf("expected weak pointer to be nil, got %p", st)
89		}
90		if i == 0 {
91			continue
92		}
93		if wt[i] == wt[i-1] {
94			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
95		}
96	}
97}
98
99func TestPointerFinalizer(t *testing.T) {
100	bt := new(T)
101	wt := weak.Make(bt)
102	done := make(chan struct{}, 1)
103	runtime.SetFinalizer(bt, func(bt *T) {
104		if wt.Strong() != nil {
105			t.Errorf("weak pointer did not go nil before finalizer ran")
106		}
107		done <- struct{}{}
108	})
109
110	// Make sure the weak pointer stays around while bt is live.
111	runtime.GC()
112	if wt.Strong() == nil {
113		t.Errorf("weak pointer went nil too soon")
114	}
115	runtime.KeepAlive(bt)
116
117	// bt is no longer referenced.
118	//
119	// Run one cycle to queue the finalizer.
120	runtime.GC()
121	if wt.Strong() != nil {
122		t.Errorf("weak pointer did not go nil when finalizer was enqueued")
123	}
124
125	// Wait for the finalizer to run.
126	<-done
127
128	// The weak pointer should still be nil after the finalizer runs.
129	runtime.GC()
130	if wt.Strong() != nil {
131		t.Errorf("weak pointer is non-nil even after finalization: %v", wt)
132	}
133}
134
135// Regression test for issue 69210.
136//
137// Weak-to-strong conversions must shade the new strong pointer, otherwise
138// that might be creating the only strong pointer to a white object which
139// is hidden in a blackened stack.
140//
141// Never fails if correct, fails with some high probability if incorrect.
142func TestIssue69210(t *testing.T) {
143	if testing.Short() {
144		t.Skip("this is a stress test that takes seconds to run on its own")
145	}
146	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
147	defer cancel()
148
149	// What we're trying to do is manufacture the conditions under which this
150	// bug happens. Specifically, we want:
151	//
152	// 1. To create a whole bunch of objects that are only weakly-pointed-to,
153	// 2. To call Strong while the GC is in the mark phase,
154	// 3. The new strong pointer to be missed by the GC,
155	// 4. The following GC cycle to mark a free object.
156	//
157	// Unfortunately, (2) and (3) are hard to control, but we can increase
158	// the likelihood by having several goroutines do (1) at once while
159	// another goroutine constantly keeps us in the GC with runtime.GC.
160	// Like throwing darts at a dart board until they land just right.
161	// We can increase the likelihood of (4) by adding some delay after
162	// creating the strong pointer, but only if it's non-nil. If it's nil,
163	// that means it was already collected in which case there's no chance
164	// of triggering the bug, so we want to retry as fast as possible.
165	// Our heap here is tiny, so the GCs will go by fast.
166	//
167	// As of 2024-09-03, removing the line that shades pointers during
168	// the weak-to-strong conversion causes this test to fail about 50%
169	// of the time.
170
171	var wg sync.WaitGroup
172	wg.Add(1)
173	go func() {
174		defer wg.Done()
175		for {
176			runtime.GC()
177
178			select {
179			case <-ctx.Done():
180				return
181			default:
182			}
183		}
184	}()
185	for range max(runtime.GOMAXPROCS(-1)-1, 1) {
186		wg.Add(1)
187		go func() {
188			defer wg.Done()
189			for {
190				for range 5 {
191					bt := new(T)
192					wt := weak.Make(bt)
193					bt = nil
194					time.Sleep(1 * time.Millisecond)
195					bt = wt.Strong()
196					if bt != nil {
197						time.Sleep(4 * time.Millisecond)
198						bt.t = bt
199						bt.a = 12
200					}
201					runtime.KeepAlive(bt)
202				}
203				select {
204				case <-ctx.Done():
205					return
206				default:
207				}
208			}
209		}()
210	}
211	wg.Wait()
212}
213