Source file src/runtime/mcleanup_test.go

     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  
     5  package runtime_test
     6  
     7  import (
     8  	"internal/runtime/atomic"
     9  	"runtime"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  	"unsafe"
    14  )
    15  
    16  func TestCleanup(t *testing.T) {
    17  	ch := make(chan bool, 1)
    18  	done := make(chan bool, 1)
    19  	want := 97531
    20  	go func() {
    21  		// allocate struct with pointer to avoid hitting tinyalloc.
    22  		// Otherwise we can't be sure when the allocation will
    23  		// be freed.
    24  		type T struct {
    25  			v int
    26  			p unsafe.Pointer
    27  		}
    28  		v := &new(T).v
    29  		*v = 97531
    30  		cleanup := func(x int) {
    31  			if x != want {
    32  				t.Errorf("cleanup %d, want %d", x, want)
    33  			}
    34  			ch <- true
    35  		}
    36  		runtime.AddCleanup(v, cleanup, 97531)
    37  		v = nil
    38  		done <- true
    39  	}()
    40  	<-done
    41  	runtime.GC()
    42  	<-ch
    43  }
    44  
    45  func TestCleanupMultiple(t *testing.T) {
    46  	ch := make(chan bool, 3)
    47  	done := make(chan bool, 1)
    48  	want := 97531
    49  	go func() {
    50  		// allocate struct with pointer to avoid hitting tinyalloc.
    51  		// Otherwise we can't be sure when the allocation will
    52  		// be freed.
    53  		type T struct {
    54  			v int
    55  			p unsafe.Pointer
    56  		}
    57  		v := &new(T).v
    58  		*v = 97531
    59  		cleanup := func(x int) {
    60  			if x != want {
    61  				t.Errorf("cleanup %d, want %d", x, want)
    62  			}
    63  			ch <- true
    64  		}
    65  		runtime.AddCleanup(v, cleanup, 97531)
    66  		runtime.AddCleanup(v, cleanup, 97531)
    67  		runtime.AddCleanup(v, cleanup, 97531)
    68  		v = nil
    69  		done <- true
    70  	}()
    71  	<-done
    72  	runtime.GC()
    73  	<-ch
    74  	<-ch
    75  	<-ch
    76  }
    77  
    78  func TestCleanupZeroSizedStruct(t *testing.T) {
    79  	type Z struct{}
    80  	z := new(Z)
    81  	runtime.AddCleanup(z, func(s string) {}, "foo")
    82  }
    83  
    84  func TestCleanupAfterFinalizer(t *testing.T) {
    85  	ch := make(chan int, 2)
    86  	done := make(chan bool, 1)
    87  	want := 97531
    88  	go func() {
    89  		// allocate struct with pointer to avoid hitting tinyalloc.
    90  		// Otherwise we can't be sure when the allocation will
    91  		// be freed.
    92  		type T struct {
    93  			v int
    94  			p unsafe.Pointer
    95  		}
    96  		v := &new(T).v
    97  		*v = 97531
    98  		finalizer := func(x *int) {
    99  			ch <- 1
   100  		}
   101  		cleanup := func(x int) {
   102  			if x != want {
   103  				t.Errorf("cleanup %d, want %d", x, want)
   104  			}
   105  			ch <- 2
   106  		}
   107  		runtime.AddCleanup(v, cleanup, 97531)
   108  		runtime.SetFinalizer(v, finalizer)
   109  		v = nil
   110  		done <- true
   111  	}()
   112  	<-done
   113  	runtime.GC()
   114  	var result int
   115  	result = <-ch
   116  	if result != 1 {
   117  		t.Errorf("result %d, want 1", result)
   118  	}
   119  	runtime.GC()
   120  	result = <-ch
   121  	if result != 2 {
   122  		t.Errorf("result %d, want 2", result)
   123  	}
   124  }
   125  
   126  func TestCleanupInteriorPointer(t *testing.T) {
   127  	ch := make(chan bool, 3)
   128  	done := make(chan bool, 1)
   129  	want := 97531
   130  	go func() {
   131  		// Allocate struct with pointer to avoid hitting tinyalloc.
   132  		// Otherwise we can't be sure when the allocation will
   133  		// be freed.
   134  		type T struct {
   135  			p unsafe.Pointer
   136  			i int
   137  			a int
   138  			b int
   139  			c int
   140  		}
   141  		ts := new(T)
   142  		ts.a = 97531
   143  		ts.b = 97531
   144  		ts.c = 97531
   145  		cleanup := func(x int) {
   146  			if x != want {
   147  				t.Errorf("cleanup %d, want %d", x, want)
   148  			}
   149  			ch <- true
   150  		}
   151  		runtime.AddCleanup(&ts.a, cleanup, 97531)
   152  		runtime.AddCleanup(&ts.b, cleanup, 97531)
   153  		runtime.AddCleanup(&ts.c, cleanup, 97531)
   154  		ts = nil
   155  		done <- true
   156  	}()
   157  	<-done
   158  	runtime.GC()
   159  	<-ch
   160  	<-ch
   161  	<-ch
   162  }
   163  
   164  func TestCleanupStop(t *testing.T) {
   165  	done := make(chan bool, 1)
   166  	go func() {
   167  		// allocate struct with pointer to avoid hitting tinyalloc.
   168  		// Otherwise we can't be sure when the allocation will
   169  		// be freed.
   170  		type T struct {
   171  			v int
   172  			p unsafe.Pointer
   173  		}
   174  		v := &new(T).v
   175  		*v = 97531
   176  		cleanup := func(x int) {
   177  			t.Error("cleanup called, want no cleanup called")
   178  		}
   179  		c := runtime.AddCleanup(v, cleanup, 97531)
   180  		c.Stop()
   181  		v = nil
   182  		done <- true
   183  	}()
   184  	<-done
   185  	runtime.GC()
   186  }
   187  
   188  func TestCleanupStopMultiple(t *testing.T) {
   189  	done := make(chan bool, 1)
   190  	go func() {
   191  		// allocate struct with pointer to avoid hitting tinyalloc.
   192  		// Otherwise we can't be sure when the allocation will
   193  		// be freed.
   194  		type T struct {
   195  			v int
   196  			p unsafe.Pointer
   197  		}
   198  		v := &new(T).v
   199  		*v = 97531
   200  		cleanup := func(x int) {
   201  			t.Error("cleanup called, want no cleanup called")
   202  		}
   203  		c := runtime.AddCleanup(v, cleanup, 97531)
   204  		c.Stop()
   205  		c.Stop()
   206  		c.Stop()
   207  		v = nil
   208  		done <- true
   209  	}()
   210  	<-done
   211  	runtime.GC()
   212  }
   213  
   214  func TestCleanupStopinterleavedMultiple(t *testing.T) {
   215  	ch := make(chan bool, 3)
   216  	done := make(chan bool, 1)
   217  	go func() {
   218  		// allocate struct with pointer to avoid hitting tinyalloc.
   219  		// Otherwise we can't be sure when the allocation will
   220  		// be freed.
   221  		type T struct {
   222  			v int
   223  			p unsafe.Pointer
   224  		}
   225  		v := &new(T).v
   226  		*v = 97531
   227  		cleanup := func(x int) {
   228  			if x != 1 {
   229  				t.Error("cleanup called, want no cleanup called")
   230  			}
   231  			ch <- true
   232  		}
   233  		runtime.AddCleanup(v, cleanup, 1)
   234  		runtime.AddCleanup(v, cleanup, 2).Stop()
   235  		runtime.AddCleanup(v, cleanup, 1)
   236  		runtime.AddCleanup(v, cleanup, 2).Stop()
   237  		runtime.AddCleanup(v, cleanup, 1)
   238  		v = nil
   239  		done <- true
   240  	}()
   241  	<-done
   242  	runtime.GC()
   243  	<-ch
   244  	<-ch
   245  	<-ch
   246  }
   247  
   248  func TestCleanupStopAfterCleanupRuns(t *testing.T) {
   249  	ch := make(chan bool, 1)
   250  	done := make(chan bool, 1)
   251  	var stop func()
   252  	go func() {
   253  		// Allocate struct with pointer to avoid hitting tinyalloc.
   254  		// Otherwise we can't be sure when the allocation will
   255  		// be freed.
   256  		type T struct {
   257  			v int
   258  			p unsafe.Pointer
   259  		}
   260  		v := &new(T).v
   261  		*v = 97531
   262  		cleanup := func(x int) {
   263  			ch <- true
   264  		}
   265  		cl := runtime.AddCleanup(v, cleanup, 97531)
   266  		v = nil
   267  		stop = cl.Stop
   268  		done <- true
   269  	}()
   270  	<-done
   271  	runtime.GC()
   272  	<-ch
   273  	stop()
   274  }
   275  
   276  func TestCleanupPointerEqualsArg(t *testing.T) {
   277  	// See go.dev/issue/71316
   278  	defer func() {
   279  		want := "runtime.AddCleanup: ptr is equal to arg, cleanup will never run"
   280  		if r := recover(); r == nil {
   281  			t.Error("want panic, test did not panic")
   282  		} else if r == want {
   283  			// do nothing
   284  		} else {
   285  			t.Errorf("wrong panic: want=%q, got=%q", want, r)
   286  		}
   287  	}()
   288  
   289  	// allocate struct with pointer to avoid hitting tinyalloc.
   290  	// Otherwise we can't be sure when the allocation will
   291  	// be freed.
   292  	type T struct {
   293  		v int
   294  		p unsafe.Pointer
   295  	}
   296  	v := &new(T).v
   297  	*v = 97531
   298  	runtime.AddCleanup(v, func(x *int) {}, v)
   299  	v = nil
   300  	runtime.GC()
   301  }
   302  
   303  // Checks to make sure cleanups aren't lost when there are a lot of them.
   304  func TestCleanupLost(t *testing.T) {
   305  	type T struct {
   306  		v int
   307  		p unsafe.Pointer
   308  	}
   309  
   310  	cleanups := 10_000
   311  	if testing.Short() {
   312  		cleanups = 100
   313  	}
   314  	n := runtime.GOMAXPROCS(-1)
   315  	want := n * cleanups
   316  	var got atomic.Uint64
   317  	var wg sync.WaitGroup
   318  	for i := range n {
   319  		wg.Add(1)
   320  		go func(i int) {
   321  			defer wg.Done()
   322  
   323  			for range cleanups {
   324  				v := &new(T).v
   325  				*v = 97531
   326  				runtime.AddCleanup(v, func(_ int) {
   327  					got.Add(1)
   328  				}, 97531)
   329  			}
   330  		}(i)
   331  	}
   332  	wg.Wait()
   333  	runtime.GC()
   334  	runtime.BlockUntilEmptyCleanupQueue(int64(10 * time.Second))
   335  	if got := int(got.Load()); got != want {
   336  		t.Errorf("expected %d cleanups to be executed, got %d", got, want)
   337  	}
   338  }
   339  

View as plain text