github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/runtime/internal/luagc/unsafepool_test.go (about) 1 package luagc 2 3 import ( 4 "sync" 5 "testing" 6 ) 7 8 // Simulate the Go runtime object management (GC, SetFinalizer). 9 type testCollector struct { 10 mx sync.Mutex 11 pending map[interface{}]func(Value) 12 } 13 14 // SetFinalizer simulates runtime.SetFinalizer. 15 func (c *testCollector) SetFinalizer(obj interface{}, finalizer interface{}) { 16 c.mx.Lock() 17 defer c.mx.Unlock() 18 if finalizer == nil { 19 delete(c.pending, obj) 20 } else { 21 c.pending[obj] = finalizer.(func(Value)) 22 } 23 } 24 25 func (c *testCollector) getFinalizer(obj interface{}) func(Value) { 26 c.mx.Lock() 27 defer c.mx.Unlock() 28 defer delete(c.pending, obj) 29 return c.pending[obj] 30 } 31 32 // GC simulate runtime.GC. 33 func (c *testCollector) GC(objs ...Value) { 34 for _, obj := range objs { 35 f := c.getFinalizer(obj) 36 if f != nil { 37 f(obj) 38 } 39 } 40 } 41 42 func (c *testCollector) FinalizerCount() int { 43 c.mx.Lock() 44 defer c.mx.Unlock() 45 return len(c.pending) 46 } 47 48 // Replace the Go runtime.SetFinalizer function with the testCollector version, 49 // for testing of UnsafePool. 50 func installTestCollector() *testCollector { 51 c := &testCollector{ 52 pending: make(map[interface{}]func(Value)), 53 } 54 setFinalizer = c.SetFinalizer 55 return c 56 } 57 58 func TestUnsafePoolFinalize(t *testing.T) { 59 const nObjs = 100 60 c := installTestCollector() 61 p := NewUnsafePool() 62 objs := make([]Value, nObjs) 63 ws := make([]WeakRef, nObjs/2) 64 65 // Create nObjs pointers to integers, each containing its index in the objs 66 // slice. Get a weakref to each one with an even index. 67 for i := range objs { 68 n := newIntPtr(i) 69 objs[i] = n 70 p.Mark(n, Finalize) 71 if i%2 == 0 { 72 ws[i/2] = p.Get(n) 73 if ws[i/2].Value() != n { 74 t.Fatalf("Expected %p, got %p", n, ws[i/2].Value()) 75 } 76 } 77 } 78 79 // Add some extra that will be collected at the end 80 for i := 0; i < nObjs; i++ { 81 n := newIntPtr(i) 82 p.Mark(n, Finalize) 83 } 84 85 // In the "GC thread", go collect all the objs in reverse index order. 86 go func() { 87 for j := nObjs - 1; j >= 0; j-- { 88 c.GC(objs[j]) 89 } 90 }() 91 92 // We get the objs in batches, but sorted in the correct order. 93 j := nObjs - 1 94 for j >= 0 { 95 for _, obj := range p.ExtractPendingFinalize() { 96 n := getInt(obj) 97 if n != j { 98 t.Fatalf("Expected %d, got %d", j, n) 99 } 100 j -= 2 101 } 102 } 103 104 // All weakrefs are still alive because the pool cleared their resurrected 105 // and reinstalled a finalizer for them. 106 for i, w := range ws { 107 if w.Value() == nil { 108 t.Fatalf("Expected ws[%d] to be non nil", i) 109 } 110 } 111 112 // Collect twice because the weakrefs were revived 113 c.GC(objs...) 114 c.GC(objs...) 115 116 // Now all revived objects are ready to be finalized 117 j = nObjs - 2 118 for _, obj := range p.ExtractPendingFinalize() { 119 n := getInt(obj) 120 if n != j { 121 t.Fatalf("Expected %d, got %d", j, n) 122 } 123 j -= 2 124 } 125 if j >= 0 { 126 t.Fatal("Missing pending finalize") 127 } 128 129 // Finalizers have run, now collecting will mark the weakrefs as dead. 130 c.GC(objs...) 131 for i, w := range ws { 132 if w.Value() != nil { 133 t.Fatalf("Expected ws[%d] to be nil", i) 134 } 135 } 136 137 extraMarked := p.ExtractAllMarkedFinalize() 138 if len(extraMarked) != nObjs { 139 t.Fatalf("Expected %d extra, got %d", nObjs, len(extraMarked)) 140 } 141 // Check the extra values can be finalized 142 for i, obj := range extraMarked { 143 n := getInt(obj) 144 if n != nObjs-1-i { 145 t.Fatalf("Expected extra %d, got %d", nObjs-1-i, n) 146 } 147 } 148 } 149 150 func TestUnsafePoolRelease(t *testing.T) { 151 const nObjs = 100 152 c := installTestCollector() 153 p := NewUnsafePool() 154 objs := make([]Value, nObjs) 155 ws := make([]WeakRef, nObjs/2) 156 157 // Create nObjs pointers to integers, each containing its index in the objs 158 // slice. Get a weakref to each one with an even index. 159 for i := range objs { 160 n := newIntPtr(i) 161 objs[i] = n 162 p.Mark(n, Release) 163 if i%2 == 0 { 164 ws[i/2] = p.Get(n) 165 if ws[i/2].Value() != n { 166 t.Fatalf("Expected %p, got %p", n, ws[i/2].Value()) 167 } 168 } 169 } 170 171 // Add some extra that will be collected at the end 172 for i := 0; i < nObjs; i++ { 173 n := newIntPtr(i) 174 p.Mark(n, Release) 175 } 176 177 // In the "GC thread", go collect all the objs in reverse index order. 178 go func() { 179 for j := nObjs - 1; j >= 0; j-- { 180 c.GC(objs[j]) 181 } 182 }() 183 184 // We get the objs in batches, but sorted in the correct order. 185 j := nObjs - 1 186 for j >= 0 { 187 for _, obj := range p.ExtractPendingRelease() { 188 n := getInt(obj) 189 if n != j { 190 t.Fatalf("Expected %d, got %d", j, n) 191 } 192 j -= 2 193 } 194 } 195 196 // All weakrefs are still alive because the pool cleared their resurrected 197 // and reinstalled a finalizer for them. 198 for i, w := range ws { 199 if w.Value() == nil { 200 t.Fatalf("Expected ws[%d] to be non nil", i) 201 } 202 } 203 204 // Collect twice because the weakrefs were revived 205 c.GC(objs...) 206 c.GC(objs...) 207 208 // Now all revived objects are ready to be finalized 209 j = nObjs - 2 210 for _, obj := range p.ExtractPendingRelease() { 211 n := getInt(obj) 212 if n != j { 213 t.Fatalf("Expected %d, got %d", j, n) 214 } 215 j -= 2 216 } 217 if j >= 0 { 218 t.Fatal("Missing pending release") 219 } 220 221 // Finalizers have run, now collecting will mark the weakrefs as dead. 222 c.GC(objs...) 223 for i, w := range ws { 224 if w.Value() != nil { 225 t.Fatalf("Expected ws[%d] to be nil", i) 226 } 227 } 228 229 // Check the extra values can be released 230 extraMarked := p.ExtractAllMarkedRelease() 231 if len(extraMarked) != nObjs { 232 t.Fatalf("Expected %d extra, got %d", nObjs, len(extraMarked)) 233 } 234 for i, obj := range extraMarked { 235 n := getInt(obj) 236 if n != nObjs-1-i { 237 t.Fatalf("Expected extra %d, got %d", nObjs-1-i, n) 238 } 239 } 240 } 241 242 func TestUnsafePoolFinalizeRelease(t *testing.T) { 243 244 // We're testing how finalize + release interact. 245 c := installTestCollector() 246 p := NewUnsafePool() 247 n := newIntPtr(1) 248 p.Mark(n, Finalize|Release) 249 w := p.Get(n) 250 251 // After collecting n, it's ready for finalizing but not release yet. 252 c.GC(n) 253 pf := p.ExtractPendingFinalize() 254 if len(pf) != 1 { 255 t.Fatalf("Expected 1 pending finalize, got %d", len(pf)) 256 } 257 pr := p.ExtractPendingRelease() 258 if len(pr) != 0 { 259 t.Fatalf("Expected 0 pending release, got %d", len(pr)) 260 } 261 262 // We can revive n still 263 v := w.Value() 264 if v == nil { 265 t.Fatal("Expected weakref to be non nil") 266 } 267 268 // This collection just cancels the reviving 269 c.GC(n) 270 pf = p.ExtractPendingFinalize() 271 if len(pf) != 0 { 272 t.Fatalf("Expected 0 pending finalize, got %d", len(pf)) 273 } 274 pr = p.ExtractPendingRelease() 275 if len(pr) != 0 { 276 t.Fatalf("Expected 0 pending release, got %d", len(pr)) 277 } 278 279 // After collection, the finalize is not re-run but the release is now run. 280 c.GC(n) 281 pf = p.ExtractPendingFinalize() 282 if len(pf) != 0 { 283 t.Fatalf("Expected 0 pending finalize, got %d", len(pf)) 284 } 285 pr = p.ExtractPendingRelease() 286 if len(pr) != 1 { 287 t.Fatalf("Expected 1 pending release, got %d", len(pr)) 288 } 289 } 290 291 func newIntPtr(n int) *intVal { 292 v := intVal(n) 293 return &v 294 } 295 296 type intVal int 297 298 var _ Value = (*intVal)(nil) 299 300 func (v *intVal) Key() Key { 301 return *v 302 } 303 304 func (v *intVal) Clone() Value { 305 clone := new(intVal) 306 *clone = *v 307 return clone 308 } 309 310 func getInt(r Value) int { 311 return int(*r.(*intVal)) 312 }