mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-31 17:59:03 +00:00
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
This commit is contained in:
Vendored
+87
@@ -0,0 +1,87 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user