var AlignUp = alignUp
-// BlockUntilEmptyFinalizerQueue blocks until either the finalizer
-// queue is emptied (and the finalizers have executed) or the timeout
-// is reached. Returns true if the finalizer queue was emptied.
func BlockUntilEmptyFinalizerQueue(timeout int64) bool {
- start := nanotime()
- for nanotime()-start < timeout {
- lock(&finlock)
- // We know the queue has been drained when both finq is nil
- // and the finalizer g has stopped executing.
- empty := finq == nil
- empty = empty && readgstatus(fing) == _Gwaiting && fing.waitreason == waitReasonFinalizerWait
- unlock(&finlock)
- if empty {
- return true
- }
- Gosched()
- }
- return false
+ return blockUntilEmptyFinalizerQueue(timeout)
}
func FrameStartLine(f *Frame) int {
return false
}
+// blockUntilEmptyFinalizerQueue blocks until either the finalizer
+// queue is emptied (and the finalizers have executed) or the timeout
+// is reached. Returns true if the finalizer queue was emptied.
+// This is used by the runtime and sync tests.
+func blockUntilEmptyFinalizerQueue(timeout int64) bool {
+ start := nanotime()
+ for nanotime()-start < timeout {
+ lock(&finlock)
+ // We know the queue has been drained when both finq is nil
+ // and the finalizer g has stopped executing.
+ empty := finq == nil
+ empty = empty && readgstatus(fing) == _Gwaiting && fing.waitreason == waitReasonFinalizerWait
+ unlock(&finlock)
+ if empty {
+ return true
+ }
+ Gosched()
+ }
+ return false
+}
+
// SetFinalizer sets the finalizer associated with obj to the provided
// finalizer function. When the garbage collector finds an unreachable block
// with an associated finalizer, it clears the association and runs
import (
"bytes"
+ "math"
"runtime"
"runtime/debug"
"sync"
+ "sync/atomic"
"testing"
+ _ "unsafe"
)
// We assume that the Once.Do tests have already covered parallelism.
panic("x")
}
+func TestOnceXGC(t *testing.T) {
+ fns := map[string]func([]byte) func(){
+ "OnceFunc": func(buf []byte) func() {
+ return sync.OnceFunc(func() { buf[0] = 1 })
+ },
+ "OnceValue": func(buf []byte) func() {
+ f := sync.OnceValue(func() any { buf[0] = 1; return nil })
+ return func() { f() }
+ },
+ "OnceValues": func(buf []byte) func() {
+ f := sync.OnceValues(func() (any, any) { buf[0] = 1; return nil, nil })
+ return func() { f() }
+ },
+ }
+ for n, fn := range fns {
+ t.Run(n, func(t *testing.T) {
+ buf := make([]byte, 1024)
+ var gc atomic.Bool
+ runtime.SetFinalizer(&buf[0], func(_ *byte) {
+ gc.Store(true)
+ })
+ f := fn(buf)
+ gcwaitfin()
+ if gc.Load() != false {
+ t.Fatal("wrapped function garbage collected too early")
+ }
+ f()
+ gcwaitfin()
+ if gc.Load() != true {
+ // Even if f is still alive, the function passed to Once(Func|Value|Values)
+ // is not kept alive after the first call to f.
+ t.Fatal("wrapped function should be garbage collected, but still live")
+ }
+ f()
+ })
+ }
+}
+
+// gcwaitfin performs garbage collection and waits for all finalizers to run.
+func gcwaitfin() {
+ runtime.GC()
+ runtime_blockUntilEmptyFinalizerQueue(math.MaxInt64)
+}
+
+//go:linkname runtime_blockUntilEmptyFinalizerQueue runtime.blockUntilEmptyFinalizerQueue
+func runtime_blockUntilEmptyFinalizerQueue(int64) bool
+
var (
onceFunc = sync.OnceFunc(func() {})