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  }