Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions runtime/internal/clite/bdwgc/bdwgc.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ func RegisterFinalizerUnreachable(
fn FinalizerFunc, cd c.Pointer,
oldFn *FinalizerFunc, oldCd *c.Pointer)

// InvokeFinalizers runs all finalizers that BDWGC has queued as ready.
Comment thread
cpunion marked this conversation as resolved.
// It returns the number of finalizers that were run.
//
//go:linkname InvokeFinalizers C.GC_invoke_finalizers
func InvokeFinalizers() c.Int

// -----------------------------------------------------------------------------

//go:linkname Enable C.GC_enable
Expand Down
16 changes: 11 additions & 5 deletions runtime/internal/lib/runtime/runtime_gc.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,25 @@ func ReadMemStats(m *runtime.MemStats) {
}

func GC() {
bdwgc.Gcollect()
runFinalizers()
// BDW finalizers are observed on a subsequent collection cycle.
collectAndRunFinalizers()
// Run one extra cycle so weak-pointer cleanup hooks (unique/weak) see
// finalized state before we trigger map cleanup callbacks.
bdwgc.Gcollect()
runFinalizers()
collectAndRunFinalizers()
unique_runtime_notifyMapCleanup()
if poolCleanup != nil {
poolCleanup()
}
}

func collectAndRunFinalizers() {
bdwgc.Gcollect()
// GC_gcollect only discovers unreachable finalizable objects. Explicitly
// drain BDWGC's ready queue so runtime.GC does not depend on a later
// allocation to invoke the callbacks that feed runFinalizers.
bdwgc.InvokeFinalizers()
runFinalizers()
}

func saturatingSub(x, y uintptr) uintptr {
if x < y {
return 0
Expand Down
50 changes: 50 additions & 0 deletions test/go/finalizer_llgo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//go:build llgo && !baremetal && !nogc

package gotest

import (
"runtime"
"testing"
_ "unsafe"
)

//go:linkname getBDWGCFinalizeOnDemand C.GC_get_finalize_on_demand
func getBDWGCFinalizeOnDemand() int32

//go:linkname setBDWGCFinalizeOnDemand C.GC_set_finalize_on_demand
func setBDWGCFinalizeOnDemand(enabled int32)

func TestRuntimeGCDrainsBDWGCFinalizersOnDemand(t *testing.T) {
// BDWGC normally may invoke ready finalizers during a later allocation.
// On-demand mode makes runtime.GC's explicit drain observable without
// relying on allocation timing. This setting is process-global, so this
// test and the neighboring finalizer tests must remain sequential.
old := getBDWGCFinalizeOnDemand()
Comment thread
cpunion marked this conversation as resolved.
setBDWGCFinalizeOnDemand(1)
t.Cleanup(func() {
setBDWGCFinalizeOnDemand(old)
})

const n = 32
finalized := make(chan int32, n)
created := make(chan struct{})
go func() {
makeFinalizerTinyObjects(n, finalized)
close(created)
}()
<-created

// InvokeFinalizers runs synchronously once BDWGC has queued an object.
// The retries only let the producer goroutine exit so that conservative
// stack and register roots no longer keep the objects reachable.
for range 8 {
runtime.Gosched()
runGCWithTimeout(t)
if len(finalized) > n/2 {
return
}
Comment thread
cpunion marked this conversation as resolved.
}
if got := len(finalized); got <= n/2 {
t.Fatalf("runtime.GC ran only %d/%d on-demand finalizers", got, n)
}
}
Loading