Source file src/runtime/mfinal_test.go

     1  // Copyright 2011 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  
     5  package runtime_test
     6  
     7  import (
     8  	"runtime"
     9  	"testing"
    10  	"time"
    11  	"unsafe"
    12  )
    13  
    14  type Tintptr *int // assignable to *int
    15  type Tint int     // *Tint implements Tinter, interface{}
    16  
    17  func (t *Tint) m() {}
    18  
    19  type Tinter interface {
    20  	m()
    21  }
    22  
    23  func TestFinalizerType(t *testing.T) {
    24  	if runtime.GOARCH != "amd64" {
    25  		t.Skipf("Skipping on non-amd64 machine")
    26  	}
    27  
    28  	ch := make(chan bool, 10)
    29  	finalize := func(x *int) {
    30  		if *x != 97531 {
    31  			t.Errorf("finalizer %d, want %d", *x, 97531)
    32  		}
    33  		ch <- true
    34  	}
    35  
    36  	var finalizerTests = []struct {
    37  		convert   func(*int) any
    38  		finalizer any
    39  	}{
    40  		{func(x *int) any { return x }, func(v *int) { finalize(v) }},
    41  		{func(x *int) any { return Tintptr(x) }, func(v Tintptr) { finalize(v) }},
    42  		{func(x *int) any { return Tintptr(x) }, func(v *int) { finalize(v) }},
    43  		{func(x *int) any { return (*Tint)(x) }, func(v *Tint) { finalize((*int)(v)) }},
    44  		{func(x *int) any { return (*Tint)(x) }, func(v Tinter) { finalize((*int)(v.(*Tint))) }},
    45  		// Test case for argument spill slot.
    46  		// If the spill slot was not counted for the frame size, it will (incorrectly) choose
    47  		// call32 as the result has (exactly) 32 bytes. When the argument actually spills,
    48  		// it clobbers the caller's frame (likely the return PC).
    49  		{func(x *int) any { return x }, func(v any) [4]int64 {
    50  			print() // force spill
    51  			finalize(v.(*int))
    52  			return [4]int64{}
    53  		}},
    54  	}
    55  
    56  	for i, tt := range finalizerTests {
    57  		done := make(chan bool, 1)
    58  		go func() {
    59  			// allocate struct with pointer to avoid hitting tinyalloc.
    60  			// Otherwise we can't be sure when the allocation will
    61  			// be freed.
    62  			type T struct {
    63  				v int
    64  				p unsafe.Pointer
    65  			}
    66  			v := &new(T).v
    67  			*v = 97531
    68  			runtime.SetFinalizer(tt.convert(v), tt.finalizer)
    69  			v = nil
    70  			done <- true
    71  		}()
    72  		<-done
    73  		runtime.GC()
    74  		select {
    75  		case <-ch:
    76  		case <-time.After(time.Second * 4):
    77  			t.Errorf("#%d: finalizer for type %T didn't run", i, tt.finalizer)
    78  		}
    79  	}
    80  }
    81  
    82  type bigValue struct {
    83  	fill uint64
    84  	it   bool
    85  	up   string
    86  }
    87  
    88  func TestFinalizerInterfaceBig(t *testing.T) {
    89  	if runtime.GOARCH != "amd64" {
    90  		t.Skipf("Skipping on non-amd64 machine")
    91  	}
    92  	ch := make(chan bool)
    93  	done := make(chan bool, 1)
    94  	go func() {
    95  		v := &bigValue{0xDEADBEEFDEADBEEF, true, "It matters not how strait the gate"}
    96  		old := *v
    97  		runtime.SetFinalizer(v, func(v any) {
    98  			i, ok := v.(*bigValue)
    99  			if !ok {
   100  				t.Errorf("finalizer called with type %T, want *bigValue", v)
   101  			}
   102  			if *i != old {
   103  				t.Errorf("finalizer called with %+v, want %+v", *i, old)
   104  			}
   105  			close(ch)
   106  		})
   107  		v = nil
   108  		done <- true
   109  	}()
   110  	<-done
   111  	runtime.GC()
   112  	select {
   113  	case <-ch:
   114  	case <-time.After(4 * time.Second):
   115  		t.Errorf("finalizer for type *bigValue didn't run")
   116  	}
   117  }
   118  
   119  func fin(v *int) {
   120  }
   121  
   122  // Verify we don't crash at least. golang.org/issue/6857
   123  func TestFinalizerZeroSizedStruct(t *testing.T) {
   124  	type Z struct{}
   125  	z := new(Z)
   126  	runtime.SetFinalizer(z, func(*Z) {})
   127  }
   128  
   129  func BenchmarkFinalizer(b *testing.B) {
   130  	const Batch = 1000
   131  	b.RunParallel(func(pb *testing.PB) {
   132  		var data [Batch]*int
   133  		for i := 0; i < Batch; i++ {
   134  			data[i] = new(int)
   135  		}
   136  		for pb.Next() {
   137  			for i := 0; i < Batch; i++ {
   138  				runtime.SetFinalizer(data[i], fin)
   139  			}
   140  			for i := 0; i < Batch; i++ {
   141  				runtime.SetFinalizer(data[i], nil)
   142  			}
   143  		}
   144  	})
   145  }
   146  
   147  func BenchmarkFinalizerRun(b *testing.B) {
   148  	b.RunParallel(func(pb *testing.PB) {
   149  		for pb.Next() {
   150  			v := new(int)
   151  			runtime.SetFinalizer(v, fin)
   152  		}
   153  	})
   154  }
   155  
   156  // One chunk must be exactly one sizeclass in size.
   157  // It should be a sizeclass not used much by others, so we
   158  // have a greater chance of finding adjacent ones.
   159  // size class 19: 320 byte objects, 25 per page, 1 page alloc at a time
   160  const objsize = 320
   161  
   162  type objtype [objsize]byte
   163  
   164  func adjChunks() (*objtype, *objtype) {
   165  	var s []*objtype
   166  
   167  	for {
   168  		c := new(objtype)
   169  		for _, d := range s {
   170  			if uintptr(unsafe.Pointer(c))+unsafe.Sizeof(*c) == uintptr(unsafe.Pointer(d)) {
   171  				return c, d
   172  			}
   173  			if uintptr(unsafe.Pointer(d))+unsafe.Sizeof(*c) == uintptr(unsafe.Pointer(c)) {
   174  				return d, c
   175  			}
   176  		}
   177  		s = append(s, c)
   178  	}
   179  }
   180  
   181  // Make sure an empty slice on the stack doesn't pin the next object in memory.
   182  func TestEmptySlice(t *testing.T) {
   183  	x, y := adjChunks()
   184  
   185  	// the pointer inside xs points to y.
   186  	xs := x[objsize:] // change objsize to objsize-1 and the test passes
   187  
   188  	fin := make(chan bool, 1)
   189  	runtime.SetFinalizer(y, func(z *objtype) { fin <- true })
   190  	runtime.GC()
   191  	select {
   192  	case <-fin:
   193  	case <-time.After(4 * time.Second):
   194  		t.Errorf("finalizer of next object in memory didn't run")
   195  	}
   196  	xsglobal = xs // keep empty slice alive until here
   197  }
   198  
   199  var xsglobal []byte
   200  
   201  func adjStringChunk() (string, *objtype) {
   202  	b := make([]byte, objsize)
   203  	for {
   204  		s := string(b)
   205  		t := new(objtype)
   206  		p := *(*uintptr)(unsafe.Pointer(&s))
   207  		q := uintptr(unsafe.Pointer(t))
   208  		if p+objsize == q {
   209  			return s, t
   210  		}
   211  	}
   212  }
   213  
   214  // Make sure an empty string on the stack doesn't pin the next object in memory.
   215  func TestEmptyString(t *testing.T) {
   216  	x, y := adjStringChunk()
   217  
   218  	ss := x[objsize:] // change objsize to objsize-1 and the test passes
   219  	fin := make(chan bool, 1)
   220  	// set finalizer on string contents of y
   221  	runtime.SetFinalizer(y, func(z *objtype) { fin <- true })
   222  	runtime.GC()
   223  	select {
   224  	case <-fin:
   225  	case <-time.After(4 * time.Second):
   226  		t.Errorf("finalizer of next string in memory didn't run")
   227  	}
   228  	ssglobal = ss // keep 0-length string live until here
   229  }
   230  
   231  var ssglobal string
   232  
   233  // Test for issue 7656.
   234  func TestFinalizerOnGlobal(t *testing.T) {
   235  	runtime.SetFinalizer(Foo1, func(p *Object1) {})
   236  	runtime.SetFinalizer(Foo2, func(p *Object2) {})
   237  	runtime.SetFinalizer(Foo1, nil)
   238  	runtime.SetFinalizer(Foo2, nil)
   239  }
   240  
   241  type Object1 struct {
   242  	Something []byte
   243  }
   244  
   245  type Object2 struct {
   246  	Something byte
   247  }
   248  
   249  var (
   250  	Foo2 = &Object2{}
   251  	Foo1 = &Object1{}
   252  )
   253  
   254  func TestDeferKeepAlive(t *testing.T) {
   255  	if *flagQuick {
   256  		t.Skip("-quick")
   257  	}
   258  
   259  	// See issue 21402.
   260  	t.Parallel()
   261  	type T *int // needs to be a pointer base type to avoid tinyalloc and its never-finalized behavior.
   262  	x := new(T)
   263  	finRun := false
   264  	runtime.SetFinalizer(x, func(x *T) {
   265  		finRun = true
   266  	})
   267  	defer runtime.KeepAlive(x)
   268  	runtime.GC()
   269  	time.Sleep(time.Second)
   270  	if finRun {
   271  		t.Errorf("finalizer ran prematurely")
   272  	}
   273  }
   274  

View as plain text