Files
tinygo/testdata/finalizerinvariants.go
T
Felipe Gené 31fff2c9a3 runtime: run syscall/js finalizers on wasm without a manual GC (#5545)
* runtime: run syscall/js finalizers on wasm without a manual GC

* runtime: address review feedback on finalizer idle GC

* runtime: clear a finished task's args pointer so its arguments are collectable

* runtime: skip the finalizer scan with a per-block registration bit

* runtime: guard the finalizer registration bitmap with gcLock

* testdata: cover finalizer invariants on every scheduler

* main_test: limit the finalizer scheduler variants to linux and darwin

* testdata: wait for the finalizer queue to drain before asserting

* testdata: make the finalizer counters atomic and wait for a known drain count

* runtime: add finalizer bookkeeping asserts under runtime_asserts

* runtime: address finalizer GC review feedback

* testdata: strengthen blocked stack finalizer test

* runtime: fix finalizer cleanup edge cases

* runtime: decouple wasm export scheduling from finalizers

* runtime: avoid redundant wakeups for re-entrant wasm exports

* runtime: simplify finalizer comments
2026-08-26 20:03:44 +02:00

88 lines
2.0 KiB
Go

package main
// Test finalizer invariants that do not require unreachable objects to be collected.
// runtime_asserts checks the finalizer table, count, and bitmap.
import (
"runtime"
"sync/atomic"
)
type obj struct{ x int }
const batch = 8
// Finalizers may run concurrently with main under the threads and cores
// schedulers, so all observations shared with a finalizer are atomic.
var (
clearedRan atomic.Int32
replacedRan atomic.Int32
reachedRan atomic.Int32
ranTwice atomic.Int32
seen [batch]atomic.Int32
reachable []*obj
)
//go:noinline
func dropCleared() {
p := &obj{}
runtime.SetFinalizer(p, func(*obj) { clearedRan.Add(1) })
runtime.SetFinalizer(p, nil)
}
//go:noinline
func dropReplaced(id int) {
p := &obj{}
runtime.SetFinalizer(p, func(*obj) { replacedRan.Add(1) })
runtime.SetFinalizer(p, func(*obj) {
if seen[id].Add(1) > 1 {
ranTwice.Add(1)
}
})
}
//go:noinline
func keepReachable(id int) {
p := &obj{x: id}
runtime.SetFinalizer(p, func(*obj) { reachedRan.Add(1) })
reachable = append(reachable, p)
}
func main() {
for i := 0; i < batch; i++ {
dropCleared()
dropReplaced(i)
keepReachable(i)
}
// Run GC to check bookkeeping and give finalizers bounded opportunities to run.
// The checks do not require finalization of an unreachable object.
for i := 0; i < 8; i++ {
runtime.GC()
runtime.Gosched()
}
// Keep the reachable objects live across every collection above.
total := 0
for _, p := range reachable {
total += p.x
}
if total != batch*(batch-1)/2 {
println("FAIL: reachable set corrupted:", total)
return
}
switch {
case clearedRan.Load() != 0:
println("FAIL: cleared finalizer ran:", clearedRan.Load())
case replacedRan.Load() != 0:
println("FAIL: replaced finalizer ran:", replacedRan.Load())
case reachedRan.Load() != 0:
println("FAIL: reachable object was finalized:", reachedRan.Load())
case ranTwice.Load() != 0:
println("FAIL: finalizer ran more than once:", ranTwice.Load())
default:
println("ok")
}
}