Source file src/weak/pointer_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 weak_test
     6  
     7  import (
     8  	"context"
     9  	"internal/goarch"
    10  	"runtime"
    11  	"sync"
    12  	"testing"
    13  	"time"
    14  	"unsafe"
    15  	"weak"
    16  )
    17  
    18  type T struct {
    19  	// N.B. T is what it is to avoid having test values get tiny-allocated
    20  	// in the same block as the weak handle, but since the fix to
    21  	// go.dev/issue/76007, this should no longer be possible.
    22  	// TODO(mknyszek): Consider using tiny-allocated values for all the tests.
    23  	t *T
    24  	a int
    25  	b int
    26  }
    27  
    28  func TestPointer(t *testing.T) {
    29  	var zero weak.Pointer[T]
    30  	if zero.Value() != nil {
    31  		t.Error("Value of zero value of weak.Pointer is not nil")
    32  	}
    33  	zeroNil := weak.Make[T](nil)
    34  	if zeroNil.Value() != nil {
    35  		t.Error("Value of weak.Make[T](nil) is not nil")
    36  	}
    37  
    38  	bt := new(T)
    39  	wt := weak.Make(bt)
    40  	if st := wt.Value(); st != bt {
    41  		t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt)
    42  	}
    43  	// bt is still referenced.
    44  	runtime.GC()
    45  
    46  	if st := wt.Value(); st != bt {
    47  		t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt)
    48  	}
    49  	// bt is no longer referenced.
    50  	runtime.GC()
    51  
    52  	if st := wt.Value(); st != nil {
    53  		t.Fatalf("expected weak pointer to be nil, got %p", st)
    54  	}
    55  }
    56  
    57  func TestPointerEquality(t *testing.T) {
    58  	var zero weak.Pointer[T]
    59  	zeroNil := weak.Make[T](nil)
    60  	if zero != zeroNil {
    61  		t.Error("weak.Make[T](nil) != zero value of weak.Pointer[T]")
    62  	}
    63  
    64  	bt := make([]*T, 10)
    65  	wt := make([]weak.Pointer[T], 10)
    66  	wo := make([]weak.Pointer[int], 10)
    67  	for i := range bt {
    68  		bt[i] = new(T)
    69  		wt[i] = weak.Make(bt[i])
    70  		wo[i] = weak.Make(&bt[i].a)
    71  	}
    72  	for i := range bt {
    73  		st := wt[i].Value()
    74  		if st != bt[i] {
    75  			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
    76  		}
    77  		if wp := weak.Make(st); wp != wt[i] {
    78  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
    79  		}
    80  		if wp := weak.Make(&st.a); wp != wo[i] {
    81  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
    82  		}
    83  		if i == 0 {
    84  			continue
    85  		}
    86  		if wt[i] == wt[i-1] {
    87  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
    88  		}
    89  	}
    90  	// bt is still referenced.
    91  	runtime.GC()
    92  	for i := range bt {
    93  		st := wt[i].Value()
    94  		if st != bt[i] {
    95  			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
    96  		}
    97  		if wp := weak.Make(st); wp != wt[i] {
    98  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
    99  		}
   100  		if wp := weak.Make(&st.a); wp != wo[i] {
   101  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
   102  		}
   103  		if i == 0 {
   104  			continue
   105  		}
   106  		if wt[i] == wt[i-1] {
   107  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
   108  		}
   109  	}
   110  	bt = nil
   111  	// bt is no longer referenced.
   112  	runtime.GC()
   113  	for i := range wt {
   114  		st := wt[i].Value()
   115  		if st != nil {
   116  			t.Fatalf("expected weak pointer to be nil, got %p", st)
   117  		}
   118  		if i == 0 {
   119  			continue
   120  		}
   121  		if wt[i] == wt[i-1] {
   122  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
   123  		}
   124  	}
   125  }
   126  
   127  func TestPointerFinalizer(t *testing.T) {
   128  	bt := new(T)
   129  	wt := weak.Make(bt)
   130  	done := make(chan struct{}, 1)
   131  	runtime.SetFinalizer(bt, func(bt *T) {
   132  		if wt.Value() != nil {
   133  			t.Errorf("weak pointer did not go nil before finalizer ran")
   134  		}
   135  		done <- struct{}{}
   136  	})
   137  
   138  	// Make sure the weak pointer stays around while bt is live.
   139  	runtime.GC()
   140  	if wt.Value() == nil {
   141  		t.Errorf("weak pointer went nil too soon")
   142  	}
   143  	runtime.KeepAlive(bt)
   144  
   145  	// bt is no longer referenced.
   146  	//
   147  	// Run one cycle to queue the finalizer.
   148  	runtime.GC()
   149  	if wt.Value() != nil {
   150  		t.Errorf("weak pointer did not go nil when finalizer was enqueued")
   151  	}
   152  
   153  	// Wait for the finalizer to run.
   154  	<-done
   155  
   156  	// The weak pointer should still be nil after the finalizer runs.
   157  	runtime.GC()
   158  	if wt.Value() != nil {
   159  		t.Errorf("weak pointer is non-nil even after finalization: %v", wt)
   160  	}
   161  }
   162  
   163  func TestPointerCleanup(t *testing.T) {
   164  	bt := new(T)
   165  	wt := weak.Make(bt)
   166  	done := make(chan struct{}, 1)
   167  	runtime.AddCleanup(bt, func(_ bool) {
   168  		if wt.Value() != nil {
   169  			t.Errorf("weak pointer did not go nil before cleanup was executed")
   170  		}
   171  		done <- struct{}{}
   172  	}, true)
   173  
   174  	// Make sure the weak pointer stays around while bt is live.
   175  	runtime.GC()
   176  	if wt.Value() == nil {
   177  		t.Errorf("weak pointer went nil too soon")
   178  	}
   179  	runtime.KeepAlive(bt)
   180  
   181  	// bt is no longer referenced.
   182  	//
   183  	// Run one cycle to queue the cleanup.
   184  	runtime.GC()
   185  	if wt.Value() != nil {
   186  		t.Errorf("weak pointer did not go nil when cleanup was enqueued")
   187  	}
   188  
   189  	// Wait for the cleanup to run.
   190  	<-done
   191  
   192  	// The weak pointer should still be nil after the cleanup runs.
   193  	runtime.GC()
   194  	if wt.Value() != nil {
   195  		t.Errorf("weak pointer is non-nil even after cleanup: %v", wt)
   196  	}
   197  }
   198  
   199  func TestPointerSize(t *testing.T) {
   200  	var p weak.Pointer[T]
   201  	size := unsafe.Sizeof(p)
   202  	if size != goarch.PtrSize {
   203  		t.Errorf("weak.Pointer[T] size = %d, want %d", size, goarch.PtrSize)
   204  	}
   205  }
   206  
   207  // Regression test for issue 69210.
   208  //
   209  // Weak-to-strong conversions must shade the new strong pointer, otherwise
   210  // that might be creating the only strong pointer to a white object which
   211  // is hidden in a blackened stack.
   212  //
   213  // Never fails if correct, fails with some high probability if incorrect.
   214  func TestIssue69210(t *testing.T) {
   215  	if testing.Short() {
   216  		t.Skip("this is a stress test that takes seconds to run on its own")
   217  	}
   218  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   219  	defer cancel()
   220  
   221  	// What we're trying to do is manufacture the conditions under which this
   222  	// bug happens. Specifically, we want:
   223  	//
   224  	// 1. To create a whole bunch of objects that are only weakly-pointed-to,
   225  	// 2. To call Value while the GC is in the mark phase,
   226  	// 3. The new strong pointer to be missed by the GC,
   227  	// 4. The following GC cycle to mark a free object.
   228  	//
   229  	// Unfortunately, (2) and (3) are hard to control, but we can increase
   230  	// the likelihood by having several goroutines do (1) at once while
   231  	// another goroutine constantly keeps us in the GC with runtime.GC.
   232  	// Like throwing darts at a dart board until they land just right.
   233  	// We can increase the likelihood of (4) by adding some delay after
   234  	// creating the strong pointer, but only if it's non-nil. If it's nil,
   235  	// that means it was already collected in which case there's no chance
   236  	// of triggering the bug, so we want to retry as fast as possible.
   237  	// Our heap here is tiny, so the GCs will go by fast.
   238  	//
   239  	// As of 2024-09-03, removing the line that shades pointers during
   240  	// the weak-to-strong conversion causes this test to fail about 50%
   241  	// of the time.
   242  
   243  	var wg sync.WaitGroup
   244  	wg.Add(1)
   245  	go func() {
   246  		defer wg.Done()
   247  		for {
   248  			runtime.GC()
   249  
   250  			select {
   251  			case <-ctx.Done():
   252  				return
   253  			default:
   254  			}
   255  		}
   256  	}()
   257  	for range max(runtime.GOMAXPROCS(-1)-1, 1) {
   258  		wg.Add(1)
   259  		go func() {
   260  			defer wg.Done()
   261  			for {
   262  				for range 5 {
   263  					bt := new(T)
   264  					wt := weak.Make(bt)
   265  					bt = nil
   266  					time.Sleep(1 * time.Millisecond)
   267  					bt = wt.Value()
   268  					if bt != nil {
   269  						time.Sleep(4 * time.Millisecond)
   270  						bt.t = bt
   271  						bt.a = 12
   272  					}
   273  					runtime.KeepAlive(bt)
   274  				}
   275  				select {
   276  				case <-ctx.Done():
   277  					return
   278  				default:
   279  				}
   280  			}
   281  		}()
   282  	}
   283  	wg.Wait()
   284  }
   285  
   286  func TestIssue70739(t *testing.T) {
   287  	x := make([]*int, 4<<16)
   288  	wx1 := weak.Make(&x[1<<16])
   289  	wx2 := weak.Make(&x[1<<16])
   290  	if wx1 != wx2 {
   291  		t.Fatal("failed to look up special and made duplicate weak handle; see issue #70739")
   292  	}
   293  }
   294  
   295  var immortal T
   296  
   297  func TestImmortalPointer(t *testing.T) {
   298  	w0 := weak.Make(&immortal)
   299  	if weak.Make(&immortal) != w0 {
   300  		t.Error("immortal weak pointers to the same pointer not equal")
   301  	}
   302  	w0a := weak.Make(&immortal.a)
   303  	w0b := weak.Make(&immortal.b)
   304  	if w0a == w0b {
   305  		t.Error("separate immortal pointers (same object) have the same pointer")
   306  	}
   307  	if got, want := w0.Value(), &immortal; got != want {
   308  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   309  	}
   310  	if got, want := w0a.Value(), &immortal.a; got != want {
   311  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   312  	}
   313  	if got, want := w0b.Value(), &immortal.b; got != want {
   314  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   315  	}
   316  
   317  	// Run a couple of cycles.
   318  	runtime.GC()
   319  	runtime.GC()
   320  
   321  	// All immortal weak pointers should never get cleared.
   322  	if got, want := w0.Value(), &immortal; got != want {
   323  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   324  	}
   325  	if got, want := w0a.Value(), &immortal.a; got != want {
   326  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   327  	}
   328  	if got, want := w0b.Value(), &immortal.b; got != want {
   329  		t.Errorf("immortal weak pointer to %p has unexpected Value %p", want, got)
   330  	}
   331  }
   332  
   333  func TestPointerTiny(t *testing.T) {
   334  	runtime.GC() // Clear tiny-alloc caches.
   335  
   336  	const N = 1000
   337  	wps := make([]weak.Pointer[int], N)
   338  	for i := range N {
   339  		// N.B. *x is just an int, so the value is very likely
   340  		// tiny-allocated alongside the weak handle, assuming bug
   341  		// from go.dev/issue/76007 exists.
   342  		x := new(int)
   343  		*x = i
   344  		wps[i] = weak.Make(x)
   345  	}
   346  
   347  	// Get the cleanups to start running.
   348  	runtime.GC()
   349  
   350  	// Expect at least 3/4ths of the weak pointers to have gone nil.
   351  	//
   352  	// Note that we provide some leeway since it's possible our allocation
   353  	// gets grouped with some other long-lived tiny allocation, but this
   354  	// shouldn't be the case for the vast majority of allocations.
   355  	n := 0
   356  	for _, wp := range wps {
   357  		if wp.Value() == nil {
   358  			n++
   359  		}
   360  	}
   361  	const want = 3 * N / 4
   362  	if n < want {
   363  		t.Fatalf("not enough weak pointers are nil: expected at least %v, got %v", want, n)
   364  	}
   365  }
   366  

View as plain text