...

Source file src/weak/pointer_test.go

Documentation: weak

     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. This must contain a pointer, otherwise the weak handle might get placed
    20  	// in a tiny block making the tests in this package flaky.
    21  	t *T
    22  	a int
    23  }
    24  
    25  func TestPointer(t *testing.T) {
    26  	var zero weak.Pointer[T]
    27  	if zero.Value() != nil {
    28  		t.Error("Value of zero value of weak.Pointer is not nil")
    29  	}
    30  	zeroNil := weak.Make[T](nil)
    31  	if zeroNil.Value() != nil {
    32  		t.Error("Value of weak.Make[T](nil) is not nil")
    33  	}
    34  
    35  	bt := new(T)
    36  	wt := weak.Make(bt)
    37  	if st := wt.Value(); st != bt {
    38  		t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt)
    39  	}
    40  	// bt is still referenced.
    41  	runtime.GC()
    42  
    43  	if st := wt.Value(); st != bt {
    44  		t.Fatalf("weak pointer is not the same as strong pointer after GC: %p vs. %p", st, bt)
    45  	}
    46  	// bt is no longer referenced.
    47  	runtime.GC()
    48  
    49  	if st := wt.Value(); st != nil {
    50  		t.Fatalf("expected weak pointer to be nil, got %p", st)
    51  	}
    52  }
    53  
    54  func TestPointerEquality(t *testing.T) {
    55  	var zero weak.Pointer[T]
    56  	zeroNil := weak.Make[T](nil)
    57  	if zero != zeroNil {
    58  		t.Error("weak.Make[T](nil) != zero value of weak.Pointer[T]")
    59  	}
    60  
    61  	bt := make([]*T, 10)
    62  	wt := make([]weak.Pointer[T], 10)
    63  	wo := make([]weak.Pointer[int], 10)
    64  	for i := range bt {
    65  		bt[i] = new(T)
    66  		wt[i] = weak.Make(bt[i])
    67  		wo[i] = weak.Make(&bt[i].a)
    68  	}
    69  	for i := range bt {
    70  		st := wt[i].Value()
    71  		if st != bt[i] {
    72  			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
    73  		}
    74  		if wp := weak.Make(st); wp != wt[i] {
    75  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
    76  		}
    77  		if wp := weak.Make(&st.a); wp != wo[i] {
    78  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
    79  		}
    80  		if i == 0 {
    81  			continue
    82  		}
    83  		if wt[i] == wt[i-1] {
    84  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
    85  		}
    86  	}
    87  	// bt is still referenced.
    88  	runtime.GC()
    89  	for i := range bt {
    90  		st := wt[i].Value()
    91  		if st != bt[i] {
    92  			t.Fatalf("weak pointer is not the same as strong pointer: %p vs. %p", st, bt[i])
    93  		}
    94  		if wp := weak.Make(st); wp != wt[i] {
    95  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wt[i])
    96  		}
    97  		if wp := weak.Make(&st.a); wp != wo[i] {
    98  			t.Fatalf("new weak pointer not equal to existing weak pointer: %v vs. %v", wp, wo[i])
    99  		}
   100  		if i == 0 {
   101  			continue
   102  		}
   103  		if wt[i] == wt[i-1] {
   104  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
   105  		}
   106  	}
   107  	bt = nil
   108  	// bt is no longer referenced.
   109  	runtime.GC()
   110  	for i := range bt {
   111  		st := wt[i].Value()
   112  		if st != nil {
   113  			t.Fatalf("expected weak pointer to be nil, got %p", st)
   114  		}
   115  		if i == 0 {
   116  			continue
   117  		}
   118  		if wt[i] == wt[i-1] {
   119  			t.Fatalf("expected weak pointers to not be equal to each other, but got %v", wt[i])
   120  		}
   121  	}
   122  }
   123  
   124  func TestPointerFinalizer(t *testing.T) {
   125  	bt := new(T)
   126  	wt := weak.Make(bt)
   127  	done := make(chan struct{}, 1)
   128  	runtime.SetFinalizer(bt, func(bt *T) {
   129  		if wt.Value() != nil {
   130  			t.Errorf("weak pointer did not go nil before finalizer ran")
   131  		}
   132  		done <- struct{}{}
   133  	})
   134  
   135  	// Make sure the weak pointer stays around while bt is live.
   136  	runtime.GC()
   137  	if wt.Value() == nil {
   138  		t.Errorf("weak pointer went nil too soon")
   139  	}
   140  	runtime.KeepAlive(bt)
   141  
   142  	// bt is no longer referenced.
   143  	//
   144  	// Run one cycle to queue the finalizer.
   145  	runtime.GC()
   146  	if wt.Value() != nil {
   147  		t.Errorf("weak pointer did not go nil when finalizer was enqueued")
   148  	}
   149  
   150  	// Wait for the finalizer to run.
   151  	<-done
   152  
   153  	// The weak pointer should still be nil after the finalizer runs.
   154  	runtime.GC()
   155  	if wt.Value() != nil {
   156  		t.Errorf("weak pointer is non-nil even after finalization: %v", wt)
   157  	}
   158  }
   159  
   160  func TestPointerSize(t *testing.T) {
   161  	var p weak.Pointer[T]
   162  	size := unsafe.Sizeof(p)
   163  	if size != goarch.PtrSize {
   164  		t.Errorf("weak.Pointer[T] size = %d, want %d", size, goarch.PtrSize)
   165  	}
   166  }
   167  
   168  // Regression test for issue 69210.
   169  //
   170  // Weak-to-strong conversions must shade the new strong pointer, otherwise
   171  // that might be creating the only strong pointer to a white object which
   172  // is hidden in a blackened stack.
   173  //
   174  // Never fails if correct, fails with some high probability if incorrect.
   175  func TestIssue69210(t *testing.T) {
   176  	if testing.Short() {
   177  		t.Skip("this is a stress test that takes seconds to run on its own")
   178  	}
   179  	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
   180  	defer cancel()
   181  
   182  	// What we're trying to do is manufacture the conditions under which this
   183  	// bug happens. Specifically, we want:
   184  	//
   185  	// 1. To create a whole bunch of objects that are only weakly-pointed-to,
   186  	// 2. To call Value while the GC is in the mark phase,
   187  	// 3. The new strong pointer to be missed by the GC,
   188  	// 4. The following GC cycle to mark a free object.
   189  	//
   190  	// Unfortunately, (2) and (3) are hard to control, but we can increase
   191  	// the likelihood by having several goroutines do (1) at once while
   192  	// another goroutine constantly keeps us in the GC with runtime.GC.
   193  	// Like throwing darts at a dart board until they land just right.
   194  	// We can increase the likelihood of (4) by adding some delay after
   195  	// creating the strong pointer, but only if it's non-nil. If it's nil,
   196  	// that means it was already collected in which case there's no chance
   197  	// of triggering the bug, so we want to retry as fast as possible.
   198  	// Our heap here is tiny, so the GCs will go by fast.
   199  	//
   200  	// As of 2024-09-03, removing the line that shades pointers during
   201  	// the weak-to-strong conversion causes this test to fail about 50%
   202  	// of the time.
   203  
   204  	var wg sync.WaitGroup
   205  	wg.Add(1)
   206  	go func() {
   207  		defer wg.Done()
   208  		for {
   209  			runtime.GC()
   210  
   211  			select {
   212  			case <-ctx.Done():
   213  				return
   214  			default:
   215  			}
   216  		}
   217  	}()
   218  	for range max(runtime.GOMAXPROCS(-1)-1, 1) {
   219  		wg.Add(1)
   220  		go func() {
   221  			defer wg.Done()
   222  			for {
   223  				for range 5 {
   224  					bt := new(T)
   225  					wt := weak.Make(bt)
   226  					bt = nil
   227  					time.Sleep(1 * time.Millisecond)
   228  					bt = wt.Value()
   229  					if bt != nil {
   230  						time.Sleep(4 * time.Millisecond)
   231  						bt.t = bt
   232  						bt.a = 12
   233  					}
   234  					runtime.KeepAlive(bt)
   235  				}
   236  				select {
   237  				case <-ctx.Done():
   238  					return
   239  				default:
   240  				}
   241  			}
   242  		}()
   243  	}
   244  	wg.Wait()
   245  }
   246  
   247  func TestIssue70739(t *testing.T) {
   248  	x := make([]*int, 4<<16)
   249  	wx1 := weak.Make(&x[1<<16])
   250  	wx2 := weak.Make(&x[1<<16])
   251  	if wx1 != wx2 {
   252  		t.Fatal("failed to look up special and made duplicate weak handle; see issue #70739")
   253  	}
   254  }
   255  

View as plain text