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